├── .eslintignore ├── .npmrc ├── .gitignore ├── .travis.yml ├── examples ├── config │ ├── special.json │ └── config.json ├── plugin │ ├── config │ │ └── config.json │ └── nemo-cookie.js ├── chain.js ├── setup.js ├── setupWithConfigDir.js ├── setupWithPlugin.js └── rawchain.js ├── test ├── config │ ├── development.json │ └── config.json ├── driverconfig.chrome.js ├── plugin │ └── sample.js ├── util │ └── index.js ├── proxy.js ├── plugin.js ├── config.js ├── override.js └── constructor.js ├── .eslintrc ├── lib ├── promise.js ├── install.js ├── plugin.js ├── setup.js ├── configure.js └── driver.js ├── .github └── workflows │ └── node.js.yml ├── LICENSE.txt ├── package.json ├── CHANGELOG.md ├── index.js └── README.md /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | detect_chromedriver_version=true 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .editorconfig 3 | *.log* 4 | .idea 5 | _site 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | 3 | node_js: 4 | - "12" 5 | - "14" 6 | 7 | addons: 8 | chrome: stable 9 | -------------------------------------------------------------------------------- /examples/config/special.json: -------------------------------------------------------------------------------- 1 | { 2 | "driver": { 3 | "browser": "phantomjs" 4 | }, 5 | "data": { 6 | "baseUrl": "https://www.paypal.com" 7 | } 8 | } -------------------------------------------------------------------------------- /examples/config/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "driver": { 3 | "browser": "config:BROWSER" 4 | }, 5 | "data": { 6 | "baseUrl": "https://www.paypal.com" 7 | }, 8 | "BROWSER": "firefox" 9 | } -------------------------------------------------------------------------------- /examples/plugin/config/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "driver": { 3 | "browser": "firefox" 4 | }, 5 | "data": { 6 | "baseUrl": "https://www.paypal.com" 7 | }, 8 | "plugins": { 9 | "cookie": { 10 | "module": "path:./nemo-cookie" 11 | } 12 | } 13 | } -------------------------------------------------------------------------------- /test/config/development.json: -------------------------------------------------------------------------------- 1 | { 2 | "driver": { 3 | "builders": { 4 | "withCapabilities": [ 5 | { 6 | "browserName": "chrome", 7 | "chromeOptions": { 8 | "args": [ 9 | "headless", 10 | "window-size=1200,800", 11 | "disable-dev-shm-usage" 12 | ] 13 | } 14 | 15 | } 16 | ] 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "eslint:recommended" 4 | ], 5 | "parserOptions": { 6 | "ecmaVersion": 6, 7 | "sourceType": "module" 8 | }, 9 | "plugins": [ 10 | "json" 11 | ], 12 | "env": { 13 | "es6": true, 14 | "jasmine": true, 15 | "node": true, 16 | "mocha": true, 17 | "browser": true, 18 | "builtin": true 19 | }, 20 | "rules": { 21 | "no-console": 0, 22 | "no-undef": 0, 23 | "no-unused-vars": 0 24 | } 25 | } -------------------------------------------------------------------------------- /test/driverconfig.chrome.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | builders: { 3 | withCapabilities: [ 4 | { 5 | browserName: 'chrome', 6 | chromeOptions: { 7 | args: [ 8 | 'headless', 9 | 'window-size=1200,800', 10 | 'disable-dev-shm-usage' 11 | ] 12 | } 13 | 14 | } 15 | ] 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /examples/chain.js: -------------------------------------------------------------------------------- 1 | const Nemo = require('../'); 2 | const config = { 3 | 'driver': { 4 | 'browser': 'phantomjs' 5 | }, 6 | 'data': { 7 | 'baseUrl': 'http://www.google.com' 8 | } 9 | }; 10 | 11 | function recall() { 12 | console.log('start recall'); 13 | Nemo(config, function (err, nemo) { 14 | nemo.driver.get(nemo.data.baseUrl); 15 | nemo.driver.quit().then(function () { 16 | console.log('and again'); 17 | recall(); 18 | }); 19 | 20 | }); 21 | } 22 | recall(); 23 | -------------------------------------------------------------------------------- /test/plugin/sample.js: -------------------------------------------------------------------------------- 1 | const async = require('async'); 2 | module.exports = { 3 | 'setup': function (whoami, nemo, callback) { 4 | if (arguments.length === 2) { 5 | callback = nemo; 6 | nemo = whoami; 7 | whoami = 'sample'; 8 | } 9 | if (whoami === 'crap plugin') { 10 | throw new Error('Sorry I wrote a crap plugin'); 11 | } 12 | nemo[whoami] = {}; 13 | nemo[whoami].isDriverSetup = !!nemo.driver.get; 14 | process.nextTick(function () { 15 | callback(null); 16 | }); 17 | 18 | } 19 | }; -------------------------------------------------------------------------------- /examples/setup.js: -------------------------------------------------------------------------------- 1 | var Nemo = require('../'); 2 | Nemo({ 3 | 'driver': { 4 | 'browser': 'firefox' 5 | }, 6 | 'data': { 7 | 'baseUrl': 'https://www.paypal.com' 8 | } 9 | }, function (err, nemo) { 10 | //always check for errors! 11 | if (err) { 12 | console.log('Error during Nemo setup', err); 13 | } 14 | nemo.driver.get(nemo.data.baseUrl); 15 | nemo.driver.getCapabilities(). 16 | then(function (caps) { 17 | console.log('Nemo successfully launched', caps.get('browserName')); 18 | }); 19 | nemo.driver.quit(); 20 | }); 21 | -------------------------------------------------------------------------------- /examples/setupWithConfigDir.js: -------------------------------------------------------------------------------- 1 | const Nemo = require('../'); 2 | 3 | //passing __dirname as the first argument tells confit to 4 | //look in __dirname + '/config' for config files 5 | Nemo(__dirname, function (err, nemo) { 6 | //always check for errors! 7 | if (err) { 8 | console.log('Error during Nemo setup', err); 9 | } 10 | 11 | nemo.driver.get(nemo.data.baseUrl); 12 | nemo.driver.getCapabilities(). 13 | then(function (caps) { 14 | console.info('Nemo successfully launched', caps.caps_.browserName); 15 | }); 16 | nemo.driver.quit(); 17 | }); 18 | -------------------------------------------------------------------------------- /lib/promise.js: -------------------------------------------------------------------------------- 1 | const wd = require('selenium-webdriver'); 2 | 3 | module.exports = function () { 4 | //return a nodejs promise or webdriver promise 5 | let promiz; 6 | let wdPromiz = wd.promise.defer(); 7 | let fulfill = function (n) { 8 | wdPromiz.fulfill(n); 9 | }; 10 | let reject = function (err) { 11 | wdPromiz.reject(err); 12 | }; 13 | promiz = global.Promise ? new Promise(function (good, bad) { 14 | fulfill = good; 15 | reject = bad; 16 | }) : wdPromiz.promise; 17 | return {promise: promiz, fulfill, reject}; 18 | }; 19 | -------------------------------------------------------------------------------- /test/util/index.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports.waitForJSReady = function waitForJSReady(nemo) { 3 | return nemo.driver.wait(function() { 4 | //console.log('execute waitForJSReady'); 5 | return nemo.driver.executeScript(function() { 6 | if (window.$) { 7 | return $('body').data('loaded'); 8 | } 9 | return false; 10 | }); 11 | } 12 | , 5000, 'JavaScript didn\'t load'); 13 | }; 14 | 15 | module.exports.doneSuccess = function (done) { 16 | return function () { 17 | done(); 18 | }; 19 | }; 20 | 21 | module.exports.doneError = function (done) { 22 | return function (err) { 23 | done(err); 24 | }; 25 | }; -------------------------------------------------------------------------------- /examples/setupWithPlugin.js: -------------------------------------------------------------------------------- 1 | const Nemo = require('../'); 2 | const path = require('path'); 3 | const basedir = path.resolve(__dirname, 'plugin'); 4 | 5 | Nemo(basedir, function (err, nemo) { 6 | //always check for errors! 7 | if (err) { 8 | console.log('Error during Nemo setup', err); 9 | } 10 | nemo.driver.getCapabilities(). 11 | then(function (caps) { 12 | console.info('Nemo successfully launched', caps.caps_.browserName); 13 | }); 14 | nemo.driver.get(nemo.data.baseUrl); 15 | nemo.cookie.deleteAll(); 16 | nemo.cookie.set('foo', 'bar'); 17 | nemo.cookie.showAll(); 18 | nemo.cookie.deleteAll(); 19 | nemo.cookie.showAll(); 20 | nemo.driver.quit(); 21 | }); 22 | -------------------------------------------------------------------------------- /test/config/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": { 3 | "sample": { 4 | "module": "path:plugin/sample" 5 | }, 6 | "preDriver": { 7 | "module": "path:plugin/sample", 8 | "priority": 99, 9 | "arguments": ["preDriver"] 10 | }, 11 | "postDriver": { 12 | "module": "path:plugin/sample", 13 | "priority": 101, 14 | "arguments": ["postDriver"] 15 | } 16 | }, 17 | "driver": { 18 | "builders": { 19 | "withCapabilities": [ 20 | { 21 | "browserName": "chrome", 22 | "chromeOptions": { 23 | "args": [ 24 | "headless", 25 | "window-size=1200,800", 26 | "disable-dev-shm-usage" 27 | ] 28 | } 29 | 30 | } 31 | ] 32 | } 33 | }, 34 | "data": { 35 | "baseUrl": "https://www.google.com", 36 | "passThroughFromJson": true 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /examples/rawchain.js: -------------------------------------------------------------------------------- 1 | const webdriver = require('selenium-webdriver'); 2 | const async = require('async'); 3 | 4 | 5 | function driversetup(nemo) { 6 | var d = webdriver.promise.defer(); 7 | async.waterfall([function setup(cb) { 8 | nemo.driver = new webdriver 9 | .Builder() 10 | .forBrowser('phantomjs') 11 | .build(); 12 | cb(null, nemo); 13 | }], function (err, result) { 14 | d.fulfill(result); 15 | }); 16 | return d; 17 | } 18 | 19 | function Nemo(cb) { 20 | var nemo = {'foo': true}; 21 | driversetup(nemo).then(function(_nemo) { 22 | nemo.driver = _nemo.driver; 23 | cb(); 24 | }); 25 | return nemo; 26 | } 27 | 28 | function recall() { 29 | console.log('start recall'); 30 | var nemo = Nemo(function() { 31 | nemo.driver.getCapabilities(); 32 | nemo.driver.quit().then(function () { 33 | console.log('and again'); 34 | recall(); 35 | }); 36 | }); 37 | 38 | } 39 | recall(); 40 | -------------------------------------------------------------------------------- /.github/workflows/node.js.yml: -------------------------------------------------------------------------------- 1 | # This workflow will do a clean install of node dependencies, cache/restore them, build the source code and run tests across different versions of node 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions 3 | 4 | name: Node.js CI 5 | 6 | on: 7 | push: 8 | branches: [ master ] 9 | pull_request: 10 | branches: [ master ] 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-latest 16 | 17 | strategy: 18 | matrix: 19 | node-version: [12.x, 14.x, 16.x] 20 | # See supported Node.js release schedule at https://nodejs.org/en/about/releases/ 21 | 22 | steps: 23 | - uses: actions/checkout@v2 24 | - name: Use Node.js ${{ matrix.node-version }} 25 | uses: actions/setup-node@v2 26 | with: 27 | node-version: ${{ matrix.node-version }} 28 | cache: 'npm' 29 | - run: npm i 30 | - run: npm test 31 | -------------------------------------------------------------------------------- /examples/plugin/nemo-cookie.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | 'setup': function (nemo, callback) { 3 | nemo.cookie = {}; 4 | nemo.cookie.delete = function (name) { 5 | return nemo.driver.manage().deleteCookie(name); 6 | }; 7 | nemo.cookie.deleteAll = function () { 8 | return nemo.driver.manage().deleteAllCookies(); 9 | }; 10 | nemo.cookie.set = function (name, value, path, domain, isSecure, expiry) { 11 | return nemo.driver.manage().addCookie(name, value, path, domain, isSecure, expiry) 12 | }; 13 | nemo.cookie.get = function (name) { 14 | return nemo.driver.manage().getCookie(name); 15 | }; 16 | nemo.cookie.getAll = function () { 17 | return nemo.driver.manage().getCookies(); 18 | }; 19 | nemo.cookie.showAll = function () { 20 | return nemo.cookie.getAll().then(function (cookies) { 21 | console.log('cookies', cookies); 22 | console.log('======================='); 23 | }); 24 | }; 25 | callback(null); 26 | 27 | } 28 | }; -------------------------------------------------------------------------------- /test/proxy.js: -------------------------------------------------------------------------------- 1 | const Nemo = require('../index'); 2 | const assert = require('assert'); 3 | const chromeConfig = require('./driverconfig.chrome'); 4 | 5 | describe('@proxy@ ', function () { 6 | 7 | it('should load problem loading page error', function (done) { 8 | process.env.nemoBaseDir = __dirname; 9 | Nemo({ 10 | driver: { 11 | proxyDetails: { 12 | method: 'manual', 13 | args: [{'http': 'host:1234', 'ftp': 'host:1234', 'https': 'host:1234'}] 14 | }, 15 | builders: chromeConfig.builders 16 | } 17 | }, function (err, nemo) { 18 | if (err) { 19 | return done(err); 20 | } 21 | nemo.driver.getCapabilities().then(function (caps) { 22 | var proxy = caps.get('proxy'); 23 | assert.equal(proxy.proxyType, 'manual'); 24 | assert.equal(proxy.ftpProxy, 'host:1234'); 25 | assert.equal(proxy.httpProxy, 'host:1234'); 26 | assert.equal(proxy.sslProxy, 'host:1234'); 27 | done(); 28 | 29 | }); 30 | 31 | }); 32 | }); 33 | }); 34 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | /*───────────────────────────────────────────────────────────────────────────*\ 2 | │ Copyright (C) 2014 PayPal │ 3 | │ │ 4 | │ │ 5 | │ Licensed under the Apache License, Version 2.0 (the "License"); you may │ 6 | │ not use this file except in compliance with the License. You may obtain │ 7 | │ a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 │ 8 | │ │ 9 | │ Unless required by applicable law or agreed to in writing, software │ 10 | │ distributed under the License is distributed on an "AS IS" BASIS, │ 11 | │ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. │ 12 | │ See the License for the specific language governing permissions and │ 13 | │ limitations under the License. │ 14 | \*───────────────────────────────────────────────────────────────────────────*/ -------------------------------------------------------------------------------- /lib/install.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const debug = require('debug'); 3 | const log = debug('nemo-core:log'); 4 | const error = debug('nemo-core:error'); 5 | const exec = require('child_process').exec; 6 | 7 | log.log = console.log.bind(console); 8 | error.log = console.error.bind(console); 9 | 10 | module.exports = function (version) { 11 | return function installSelenium(callback) { 12 | //check package.json 13 | var pkg = require(path.resolve(__dirname, '../package.json')); 14 | if (pkg.dependencies['selenium-webdriver'] === version) { 15 | log('selenium version %s already installed', version); 16 | return callback(null); 17 | } 18 | var save = process.env.NEMO_UNIT_TEST ? '' : '--save'; 19 | var cmd = 'npm install ' + save + ' selenium-webdriver@' + version; 20 | log('npm install cmd', cmd); 21 | exec(cmd, {cwd: path.resolve(__dirname, '..')}, 22 | function (err, stdout, stderr) { 23 | if (stdout) { 24 | log('seleniumInstall: stdout', stdout); 25 | } 26 | if (stderr) { 27 | error('seleniumInstall: stderr', stderr); 28 | } 29 | if (err !== null) { 30 | error('exec error', err); 31 | return callback(err); 32 | } 33 | callback(null); 34 | 35 | }); 36 | }; 37 | 38 | }; 39 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nemo-core", 3 | "version": "1.1.3", 4 | "description": "Node.js based configuration and plugin platform for selenium-webdriver", 5 | "scripts": { 6 | "test": "npm run lint && mocha --timeout 30000 ", 7 | "test:default": "npm run lint && mocha --timeout 30000 --reporter spec", 8 | "lint": "eslint --fix lib/* examples/* test/**/* --ext .json" 9 | }, 10 | "dependencies": { 11 | "async": "^2.6.2", 12 | "confit": "^2.0.0", 13 | "debug": "^4.1.1", 14 | "lodash": "^4.17.11", 15 | "selenium-webdriver": "^3.4.0", 16 | "shortstop-handlers": "^1.0.0", 17 | "yargs": "^13.2.4" 18 | }, 19 | "main": "index.js", 20 | "devDependencies": { 21 | "chai": "^4.2.0", 22 | "chromedriver": "^79.0.3", 23 | "eslint": "^5.16.0", 24 | "eslint-plugin-json": "^1.4.0", 25 | "geckodriver": "^1.16.2", 26 | "mocha": "^5.0.0" 27 | }, 28 | "repository": { 29 | "type": "git", 30 | "url": "git://github.com/paypal/nemo-core.git" 31 | }, 32 | "keywords": [ 33 | "Selenium, Automation" 34 | ], 35 | "engines": { 36 | "node": ">= 6.0.0" 37 | }, 38 | "author": "Matt Edelman ", 39 | "contributors": [ 40 | "Matt Edelman ", 41 | "Nilesh Kulkarni ", 42 | "Shay Davidson ", 43 | "Ashwin Hegde " 44 | ], 45 | "licenses": [ 46 | { 47 | "type": "Apache 2.0", 48 | "url": "http://www.apache.org/licenses/LICENSE-2.0.html" 49 | } 50 | ] 51 | } 52 | -------------------------------------------------------------------------------- /test/plugin.js: -------------------------------------------------------------------------------- 1 | /* global module: true, require: true, console: true */ 2 | const assert = require('assert'); 3 | const path = require('path'); 4 | const Nemo = require('../index'); 5 | const chromeConfig = require('./driverconfig.chrome'); 6 | 7 | 8 | describe('@plugin@', function () { 9 | 10 | it('should @priorityRegister@', function (done) { 11 | process.env.nemoBaseDir = path.resolve(__dirname); 12 | 13 | Nemo(function(err, nemo) { 14 | assert.equal(nemo.preDriver.isDriverSetup, false); 15 | assert.equal(nemo.postDriver.isDriverSetup, true); 16 | nemo.driver.quit(); 17 | done(); 18 | }); 19 | }); 20 | it('should handle @nonexistPlugin@', function (done) { 21 | delete process.env.nemoBaseDir; 22 | Nemo(__dirname, { 23 | driver: chromeConfig, 24 | 'plugins': { 25 | 'noexist': { 26 | 'module': 'ModuleThatDoesNotExist' 27 | } 28 | } 29 | }, function (err) { 30 | if (err) { 31 | done(); 32 | return; 33 | } 34 | done(new Error('didnt get the correct exception')); 35 | }); 36 | }); 37 | it('should handle @failedPluginRegistration@', function (done) { 38 | delete process.env.nemoBaseDir; 39 | 40 | Nemo(__dirname, { 41 | driver: chromeConfig, 42 | plugins: { 43 | crappy: { 44 | module: 'path:plugin/sample', 45 | arguments: ['crap plugin'] 46 | } 47 | } 48 | }, function (err) { 49 | 50 | if (err && err.name && err.name === 'nemoPluginSetupError') { 51 | return done(); 52 | } 53 | else if (err) { 54 | done(err); 55 | } 56 | }); 57 | }); 58 | }); 59 | -------------------------------------------------------------------------------- /test/config.js: -------------------------------------------------------------------------------- 1 | /* global module: true, require: true, console: true */ 2 | const assert = require('assert'); 3 | const Nemo = require('../index'); 4 | const chromeConfig = require('./driverconfig.chrome'); 5 | 6 | describe('@config@', function () { 7 | process.env.NEMO_UNIT_TEST = 'true'; 8 | it('should pass confit object as nemo._config', function (done) { 9 | Nemo({ 10 | driver: chromeConfig, 11 | data: { 12 | Roger: { 13 | Federer: { 14 | is: 'perhaps the GOAT... but Novak Djokovic is in the running' 15 | } 16 | } 17 | } 18 | }, function (err, nemo) { 19 | assert.equal(err, undefined); 20 | assert(nemo._config); 21 | assert.equal(nemo._config.get('data:Roger:Federer:is'), 'perhaps the GOAT... but Novak Djokovic is in the running'); 22 | nemo.driver.quit().then(function () { 23 | done(); 24 | }); 25 | }); 26 | }); 27 | // it('should install provided @selenium.version@', function (done) { 28 | // var ver = '^2.53.1'; 29 | // Nemo({ 30 | // 'driver': { 31 | // 'browser': 'phantomjs', 32 | // 'selenium.version': ver 33 | // } 34 | // }, function (err, nemo) { 35 | // assert.equal(err, undefined); 36 | // var pac = require('selenium-webdriver/package.json'); 37 | // assert.ok(pac.version.indexOf('2.53') !== -1); 38 | // nemo.driver.quit().then(function () { 39 | // done(); 40 | // }); 41 | // }); 42 | // }); 43 | 44 | it('should throw an error for invalid @invalid.selenium.version@', function (done) { 45 | Nemo({ 46 | driver: chromeConfig 47 | }, function (err) { 48 | assert(err); 49 | done(); 50 | }); 51 | }); 52 | it('should export a Configure method', function () { 53 | return assert(Nemo.Configure && typeof Nemo.Configure === 'function'); 54 | }); 55 | it('should export a Configure method resolving to a Confit object', function () { 56 | return Nemo.Configure().then(function (confit) { 57 | return assert(confit.get); 58 | }); 59 | }); 60 | }); 61 | -------------------------------------------------------------------------------- /lib/plugin.js: -------------------------------------------------------------------------------- 1 | const debug = require('debug'); 2 | const log = debug('nemo-core:log'); 3 | const error = debug('nemo-core:error'); 4 | const Promiz = require('./promise'); 5 | 6 | log.log = console.log.bind(console); 7 | error.log = console.error.bind(console); 8 | 9 | module.exports.compare = function (a, b) { 10 | let ap, bp; 11 | ap = !Number.isNaN(a.priority) ? a.priority : Number.MIN_VALUE; 12 | bp = !Number.isNaN(b.priority) ? b.priority : Number.MIN_VALUE; 13 | ap = ap === -1 ? Number.MAX_VALUE : ap; 14 | bp = bp === -1 ? Number.MAX_VALUE : bp; 15 | return ap - bp; 16 | }; 17 | 18 | module.exports.registration = function (nemo, plugins) { 19 | log('plugin.registration start'); 20 | let promiz = Promiz(), 21 | pluginError, 22 | registerFns = []; 23 | let pluginErrored = Object.keys(plugins || {}).find(function pluginsKeys(key) { 24 | let pluginConfig = plugins[key], 25 | pluginArgs = pluginConfig.arguments || [], 26 | modulePath = pluginConfig.module, 27 | pluginModule; 28 | 29 | //register this plugin 30 | log(`register plugin ${key}`); 31 | try { 32 | pluginModule = require(modulePath); 33 | } catch (err) { 34 | pluginError = err; 35 | //returning true means we bail out of building registerFns 36 | return true; 37 | } 38 | if (pluginConfig.priority && pluginConfig.priority === 100 || Number.isNaN(pluginConfig.priority)) { 39 | pluginError = new Error(`Plugin priority not set properly for ${key}`); 40 | return true; 41 | } 42 | 43 | registerFns.push({ 44 | fn: pluginReg(nemo, pluginArgs, pluginModule), 45 | key: key, 46 | priority: pluginConfig.priority || -1 47 | }); 48 | return false; 49 | }); 50 | 51 | if (pluginErrored) { 52 | error(pluginError); 53 | promiz.reject(pluginError); 54 | 55 | } else { 56 | log(`plugin.registration fulfill with ${registerFns.length} plugins.`); 57 | promiz.fulfill(registerFns); 58 | } 59 | return promiz.promise; 60 | }; 61 | 62 | let pluginReg = function (_nemo, pluginArgs, pluginModule) { 63 | return function pluginReg(callback) { 64 | 65 | pluginArgs.push(_nemo); 66 | pluginArgs.push(callback); 67 | try { 68 | pluginModule.setup.apply(this, pluginArgs); 69 | } catch (err) { 70 | //dang, someone wrote a crap plugin 71 | error(err); 72 | let pluginSetupError = new Error('Nemo plugin threw error during setup. ' + err); 73 | pluginSetupError.name = 'nemoPluginSetupError'; 74 | callback(pluginSetupError); 75 | } 76 | }; 77 | }; -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # nemo-core CHANGELOG 2 | 3 | ## v1.1.3 4 | 5 | - Fix npm audit issues 6 | 7 | ## v1.1.1 8 | 9 | - Fix npm audit issues 10 | - Use headless chrome instead of phantomjs for unit tests 11 | 12 | ## v1.1.0 13 | 14 | - feature: allow passed in driver function 15 | - helpful when custom driver desired, for which current internal convenience methods are not suitable 16 | - allows use of latest constructors in selenium-webdriver 17 | 18 | ## nemo-core@1.0.0 19 | 20 | - renaming nemo to nemo-core 21 | 22 | ## v3.0.3 23 | 24 | - upgrading all deps and devDeps 25 | 26 | ## v3.0.2 27 | 28 | - upgrade confit to v2.3.0, removes insecure "uglify" module 29 | - add package-lock.json 30 | 31 | ## v3.0.1 32 | 33 | - upgrade "debug" dependency to resolve DoS vuln 34 | 35 | ## v3.0.0 36 | 37 | - alpha has been out and in use long enough. We don't expect further breaking API changes 38 | 39 | ## v3.0.0-alpha 40 | 41 | - Add support for selenium-webdriver@^3.0.0 42 | - refactor index.js into several lib/ modules 43 | 44 | ## v2.3.1 45 | 46 | - Fix: [#115](https://github.com/paypal/nemo/issues/115) 47 | - %j placeholder for debug module doesn't print Error objects. %s should be used instead for Strings and Error objects. 48 | %j is appropriate where we expect JSON objects and want logger to convert JSON.stringify() for us 49 | - Enhanced error message when plugin is not found 50 | - Changed default local unit tests to run on PhantomJS only 51 | - Added section to elaborate on how to run unit tests 52 | 53 | ## v2.3.0 54 | 55 | Selenium web driver was outdated and had a node security advisory. Bumped other outdated modules; 56 | 57 | - async@~0.2.8 -> async@^1.5.2 58 | - lodash@^2.4.0 -> lodash@^4.12.0 59 | - selenium-webdriver@~2.48.0 -> selenium-webdriver@^2.53.2 60 | - yargs@^3.6.0 -> yargs@^4.7.1 61 | - chai@~1.6.0 -> chai@^3.5.0 62 | - grunt@~0.4.1 -> grunt@^1.0.1 63 | - grunt-contrib-jshint@~0.7.1 -> grunt-contrib-jshint@^1.0.0 64 | 65 | ## v2.2.0 66 | 67 | * add `selenium.version` feature. Please see: https://github.com/paypal/nemo/pull/107 68 | 69 | ## v2.1.1 70 | 71 | * add resolved nemo as the second argument to the constructor's callback. See: https://github.com/paypal/nemo/pull/114 72 | 73 | ## v2.1.0 [UNPUBLISHED] 74 | 75 | * add ability to install custom selenium-webdriver version. Please see #98 76 | * unfortunately the addition of this feature caused some unintended consequences via the `npmi` module. Please see #102 77 | 78 | ## v2.0.0 79 | 80 | * lock to ES6 feature compatible version of selenium-webdriver (not guaranteed to work with node@v0.12 and earlier) 81 | -------------------------------------------------------------------------------- /test/override.js: -------------------------------------------------------------------------------- 1 | /* global module: true, require: true, console: true */ 2 | 3 | const assert = require('assert'); 4 | const Nemo = require('../index'); 5 | const firefox = require('selenium-webdriver/firefox'); 6 | 7 | describe('@override@', function () { 8 | 9 | it('@fromEnv@ over config.json data', function (done) { 10 | process.env.nemoBaseDir = __dirname; 11 | process.env.data = JSON.stringify({ 12 | baseUrl: 'http://www.ebay.com' 13 | }); 14 | Nemo(function (err, nemo) { 15 | if (err) { 16 | return done(err); 17 | } 18 | assert.equal(nemo.data.baseUrl, 'http://www.ebay.com'); 19 | nemo.driver.quit(); 20 | done(); 21 | }); 22 | }); 23 | it('@fromArg@ over config.json data', function (done) { 24 | process.env.nemoBaseDir = __dirname; 25 | 26 | Nemo({ 27 | data: { 28 | baseUrl: 'http://www.ebay.com' 29 | } 30 | }, function (err, nemo) { 31 | if (err) { 32 | return done(err); 33 | } 34 | assert.equal(nemo.data.baseUrl, 'http://www.ebay.com'); 35 | nemo.driver.quit(); 36 | done(); 37 | }); 38 | }); 39 | it('@builders@ overrides tgtBrowser abstraction', function (done) { 40 | process.env.nemoBaseDir = __dirname; 41 | const binary = new firefox.Binary(); 42 | binary.addArguments("--headless"); 43 | 44 | Nemo({ 45 | driver: { 46 | builders: { 47 | forBrowser: ['firefox'], 48 | setFirefoxOptions: [new firefox.Options().setBinary(binary)] 49 | } 50 | }, 51 | data: { 52 | baseUrl: 'http://www.ebay.com' 53 | } 54 | }, function (err, nemo) { 55 | nemo.driver.getCapabilities().then(function (caps) { 56 | assert.notEqual(caps.get('browserName'), 'chrome'); 57 | nemo.driver.quit(); 58 | done(); 59 | }); 60 | }); 61 | }); 62 | it('@driverFunction@ overrides other driver abstractions', function (done) { 63 | process.env.nemoBaseDir = __dirname; 64 | 65 | Nemo({ 66 | driver: function () { 67 | const { Builder } = require('selenium-webdriver'); 68 | const binary = new firefox.Binary(); 69 | binary.addArguments("--headless"); 70 | return new Builder().forBrowser('firefox').setFirefoxOptions(new firefox.Options().setBinary(binary)).build() 71 | }, 72 | data: { 73 | baseUrl: 'http://www.ebay.com' 74 | } 75 | }, function (err, nemo) { 76 | nemo.driver.getCapabilities().then(function (caps) { 77 | assert.notEqual(caps.get('browserName'), 'chrome'); 78 | nemo.driver.quit(); 79 | done(); 80 | }); 81 | }); 82 | }); 83 | 84 | }); 85 | -------------------------------------------------------------------------------- /lib/setup.js: -------------------------------------------------------------------------------- 1 | const Plugin = require('./plugin'); 2 | const wd = require('selenium-webdriver'); 3 | const Promiz = require('./promise'); 4 | const debug = require('debug'); 5 | const log = debug('nemo-core:log'); 6 | const error = debug('nemo-core:error'); 7 | const async = require('async'); 8 | const Driver = require('./driver'); 9 | 10 | log.log = console.log.bind(console); 11 | error.log = console.error.bind(console); 12 | 13 | 14 | let setup = function setup(config, cb) { 15 | let nemo = { 16 | 'data': config.get('data'), 17 | 'driver': {}, 18 | '_config': config 19 | }; 20 | Plugin.registration(nemo, config.get('plugins')) 21 | .then(function (registerFns) { 22 | //add driver setup 23 | registerFns.push({fn: driversetup(nemo), priority: 100}); 24 | registerFns = registerFns.sort(Plugin.compare).map(function (obj) { 25 | return obj.fn; 26 | }); 27 | registerFns.unshift(function setWebdriver(callback) { 28 | nemo.wd = wd; 29 | callback(null); 30 | }); 31 | if (config.get('driver:selenium.version')) { 32 | //install before driver setup 33 | log('Requested install of selenium version %s', config.get('driver:selenium.version')); 34 | var seleniumInstall = require('./install'); 35 | registerFns.unshift(seleniumInstall(config.get('driver:selenium.version'))); 36 | } 37 | async.waterfall(registerFns, function waterfall(err) { 38 | if (err) { 39 | cb(err); 40 | } else { 41 | cb(null, nemo); 42 | } 43 | }); 44 | }) 45 | .catch(function (err) { 46 | error(err); 47 | cb(err); 48 | }); 49 | }; 50 | 51 | var driversetup = function (_nemo) { 52 | return function driversetup(callback) { 53 | var driverConfig = _nemo._config.get('driver'); 54 | //do driver/view/locator/vars setup 55 | (Driver()).setup(driverConfig, function setupCallback(err, _driver) { 56 | if (err) { 57 | callback(err); 58 | return; 59 | } 60 | //set driver 61 | _nemo.driver = _driver; 62 | callback(null); 63 | 64 | }); 65 | }; 66 | }; 67 | 68 | 69 | 70 | 71 | module.exports = function (config) { 72 | let promiz = Promiz(); 73 | if (config.get('driver') === undefined) { 74 | var errorMessage = 'Nemo essential driver properties not found in configuration'; 75 | error(errorMessage); 76 | var badDriverProps = new Error(errorMessage); 77 | badDriverProps.name = 'nemoBadDriverProps'; 78 | process.nextTick(function () { 79 | promiz.reject(badDriverProps); 80 | }); 81 | } else { 82 | setup(config, function (err, nemo) { 83 | log('got called back'); 84 | if (err !== null) { 85 | promiz.reject(err); 86 | return; 87 | } 88 | promiz.fulfill(nemo); 89 | }); 90 | } 91 | 92 | return promiz.promise; 93 | }; -------------------------------------------------------------------------------- /lib/configure.js: -------------------------------------------------------------------------------- 1 | const debug = require('debug'); 2 | const log = debug('nemo-core:log'); 3 | const confit = require('confit'); 4 | const _ = require('lodash'); 5 | const path = require('path'); 6 | const Promiz = require('./promise'); 7 | const handlers = require('shortstop-handlers'); 8 | const yargs = require('yargs'); 9 | const error = debug('nemo-core:error'); 10 | 11 | log.log = console.log.bind(console); 12 | error.log = console.error.bind(console); 13 | 14 | module.exports = function Configure(_basedir, _configOverride) { 15 | log('_basedir %s, _configOverride %o', _basedir, _configOverride); 16 | let basedir, configOverride; 17 | //settle arguments 18 | basedir = arguments.length && typeof arguments[0] === 'string' ? arguments[0] : process.env.nemoBaseDir || undefined; 19 | configOverride = !basedir && arguments.length && typeof arguments[0] === 'object' ? arguments[0] : undefined; 20 | configOverride = !configOverride && arguments.length && arguments[1] && typeof arguments[1] === 'object' ? arguments[1] : configOverride; 21 | configOverride = !configOverride ? {} : configOverride; 22 | 23 | log('basedir %s, configOverride %o', basedir, configOverride); 24 | 25 | let prom = Promiz(); 26 | 27 | //hack because confit doesn't JSON.parse environment variables before merging 28 | //look into using shorstop handler or pseudo-handler in place of this 29 | let envdata = envToJSON('data'); 30 | let envdriver = envToJSON('driver'); 31 | let envplugins = envToJSON('plugins'); 32 | 33 | let confitOptions = { 34 | protocols: { 35 | path: handlers.path(basedir), 36 | env: handlers.env(), 37 | file: handlers.file(basedir), 38 | base64: handlers.base64(), 39 | require: handlers.require(basedir), 40 | exec: handlers.exec(basedir), 41 | glob: handlers.glob(basedir), 42 | argv: function argHandler(val) { 43 | let argv = yargs.argv; 44 | return argv[val] || ''; 45 | } 46 | } 47 | }; 48 | 49 | if (basedir) { 50 | confitOptions.basedir = path.join(basedir, 'config'); 51 | } 52 | log('confit options', confitOptions); 53 | log('confit overrides: \ndata: %o,\ndriver: %o\nplugins: %o', envdata.json, envdriver.json, envplugins.json); 54 | //merge any environment JSON into configOverride 55 | _.merge(configOverride, envdata.json, envdriver.json, envplugins.json); 56 | log('configOverride %o', configOverride); 57 | 58 | confit(confitOptions).addOverride(configOverride).create(function (err, config) { 59 | //reset env variables 60 | envdata.reset(); 61 | envdriver.reset(); 62 | envplugins.reset(); 63 | if (err) { 64 | return prom.reject(err); 65 | } 66 | prom.fulfill(config); 67 | }); 68 | return prom.promise; 69 | }; 70 | 71 | let envToJSON = function (prop) { 72 | let returnJSON = {}; 73 | let originalValue = process.env[prop]; 74 | if (originalValue === undefined) { 75 | return { 76 | 'json': {}, 77 | 'reset': function () { 78 | } 79 | }; 80 | } 81 | try { 82 | returnJSON[prop] = JSON.parse(process.env[prop]); 83 | delete process.env[prop]; 84 | } catch (err) { 85 | //noop 86 | error(err); 87 | } 88 | return { 89 | 'json': returnJSON, 90 | 'reset': function () { 91 | process.env[prop] = originalValue; 92 | } 93 | }; 94 | }; 95 | 96 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | /*───────────────────────────────────────────────────────────────────────────*\ 2 | │ Copyright (C) 2014 PayPal │ 3 | │ │ 4 | │ │ 5 | │ Licensed under the Apache License, Version 2.0 (the "License"); you may │ 6 | │ not use this file except in compliance with the License. You may obtain │ 7 | │ a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 │ 8 | │ │ 9 | │ Unless required by applicable law or agreed to in writing, software │ 10 | │ distributed under the License is distributed on an "AS IS" BASIS, │ 11 | │ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. │ 12 | │ See the License for the specific language governing permissions and │ 13 | │ limitations under the License. │ 14 | \*───────────────────────────────────────────────────────────────────────────*/ 15 | 16 | const Promiz = require('./lib/promise'); 17 | const Configure = require('./lib/configure'); 18 | const Setup = require('./lib/setup'); 19 | const debug = require('debug'); 20 | const log = debug('nemo-core:log'); 21 | const error = debug('nemo-core:error'); 22 | const _ = require('lodash'); 23 | const path = require('path'); 24 | 25 | log.log = console.log.bind(console); 26 | error.log = console.error.bind(console); 27 | 28 | /** 29 | * Represents a Nemo instance 30 | * @constructor 31 | * @param {Object} config - Object which contains any plugin registration and optionally nemoData 32 | * 33 | */ 34 | 35 | module.exports = function Nemo(_basedir, _configOverride, _cb) { 36 | log('Nemo constructor begin'); 37 | //argument vars 38 | var basedir, configOverride, cb, promiz; 39 | var nemo = {}; 40 | 41 | //check for confit object as single parameter 42 | if (arguments.length === 1 && arguments[0].get) { 43 | return Setup(arguments[0]); 44 | } 45 | 46 | //settle arguments 47 | cb = (arguments.length && typeof arguments[arguments.length - 1] === 'function') ? arguments[arguments.length - 1] : undefined; 48 | basedir = (arguments.length && typeof arguments[0] === 'string') ? arguments[0] : undefined; 49 | configOverride = (!basedir && arguments.length && typeof arguments[0] === 'object') ? arguments[0] : undefined; 50 | configOverride = (!configOverride && arguments.length && arguments[1] && typeof arguments[1] === 'object') ? arguments[1] : configOverride; 51 | basedir = basedir || process.env.nemoBaseDir || undefined; 52 | configOverride = configOverride || {}; 53 | if (!cb) { 54 | log('returning promise'); 55 | promiz = Promiz(); 56 | cb = function (err, n) { 57 | if (err) { 58 | return promiz.reject(err); 59 | } 60 | promiz.fulfill(n); 61 | }; 62 | } 63 | log('basedir', basedir); 64 | log('configOverride', configOverride); 65 | Configure(basedir, configOverride) 66 | .then(function (config) { 67 | log('Configure complete'); 68 | return Setup(config); 69 | }) 70 | .then(function (_nemo) { 71 | log('Setup complete'); 72 | _.merge(nemo, _nemo); 73 | return cb(null, nemo); 74 | }) 75 | .catch(cb); 76 | return promiz && promiz.promise || nemo; 77 | }; 78 | 79 | module.exports.Configure = Configure; -------------------------------------------------------------------------------- /test/constructor.js: -------------------------------------------------------------------------------- 1 | /* global module: true, require: true, console: true */ 2 | 3 | const assert = require('assert'); 4 | const Nemo = require('../index'); 5 | const chromeConfig = require('./driverconfig.chrome'); 6 | 7 | describe('@constructor@', function () { 8 | it('should return a promise with @noArguments@', function (done) { 9 | Nemo().then(function () { 10 | done(new Error('should have failed with nemoBadDriverProps')); 11 | }).catch(function () { 12 | done(); 13 | }) 14 | }); 15 | it('should return a promise with @noCallback@', function (done) { 16 | Nemo({ 17 | 'driver': chromeConfig 18 | }).then(function () { 19 | done(); 20 | }).catch(function (err) { 21 | done(err); 22 | }) 23 | }); 24 | it('should throw an error with @noDriverProps@', function (done) { 25 | 26 | Nemo(function (err) { 27 | if (err.name === 'nemoBadDriverProps') { 28 | done(); 29 | return; 30 | } 31 | done(new Error('didnt get back the expected error')); 32 | }); 33 | 34 | }); 35 | 36 | it('should launch nemo with @noConfigPath@overrideArg@', function (done) { 37 | delete process.env.nemoBaseDir; 38 | Nemo({ 39 | driver: chromeConfig 40 | }, function (err, nemo) { 41 | assert(nemo.driver); 42 | nemo.driver.get('http://www.google.com'); 43 | nemo.driver.quit().then(function () { 44 | done(); 45 | }); 46 | 47 | }); 48 | }); 49 | 50 | 51 | it('should launch nemo with @envConfigPath@noOverrideArg@', function (done) { 52 | process.env.nemoBaseDir = __dirname; 53 | Nemo(function (err, nemo) { 54 | assert(nemo.driver); 55 | assert(nemo.data.passThroughFromJson); 56 | nemo.driver.get(nemo.data.baseUrl); 57 | nemo.driver.quit().then(function () { 58 | done(); 59 | }); 60 | }); 61 | }); 62 | 63 | 64 | it('should launch nemo with @argConfigPath@noOverrideArg@', function (done) { 65 | var nemoBaseDir = __dirname; 66 | 67 | Nemo(nemoBaseDir, function (err, nemo) { 68 | assert(nemo.driver); 69 | assert(nemo.data.passThroughFromJson); 70 | nemo.driver.get(nemo.data.baseUrl); 71 | nemo.driver.quit().then(function () { 72 | done(); 73 | }); 74 | }); 75 | }); 76 | it('should launch nemo with @allArgs@', function (done) { 77 | var nemoBaseDir = __dirname; 78 | Nemo(nemoBaseDir, { 79 | data: { 80 | argPassthrough: true 81 | } 82 | }, function (err, nemo) { 83 | assert(nemo.driver); 84 | assert(nemo.data.passThroughFromJson); 85 | assert(nemo.data.argPassthrough); 86 | nemo.driver.get(nemo.data.baseUrl); 87 | nemo.driver.quit().then(function () { 88 | done(); 89 | }); 90 | }); 91 | }); 92 | it('should return the resolved nemo object when the callback is called', function (done) { 93 | var nemoBaseDir = __dirname; 94 | var returnedNemo = Nemo(nemoBaseDir, { 95 | data: { 96 | argPassthrough: true 97 | } 98 | }, function (err, nemo) { 99 | assert.equal(nemo, returnedNemo); 100 | nemo.driver.quit().then(function () { 101 | done(); 102 | }); 103 | }); 104 | }); 105 | it('should resolve when a Confit object is the only parameter', function () { 106 | return Nemo.Configure({driver: {browser: 'phantomjs'}}).then(function (confit) { 107 | return Nemo(confit); 108 | }) 109 | .then(function (nemo) { 110 | return assert(nemo.driver); 111 | }); 112 | }); 113 | }); 114 | -------------------------------------------------------------------------------- /lib/driver.js: -------------------------------------------------------------------------------- 1 | /*───────────────────────────────────────────────────────────────────────────*\ 2 | │ Copyright (C) 2014 PayPal │ 3 | │ │ 4 | │ │ 5 | │ Licensed under the Apache License, Version 2.0 (the "License"); you may │ 6 | │ not use this file except in compliance with the License. You may obtain │ 7 | │ a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 │ 8 | │ │ 9 | │ Unless required by applicable law or agreed to in writing, software │ 10 | │ distributed under the License is distributed on an "AS IS" BASIS, │ 11 | │ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. │ 12 | │ See the License for the specific language governing permissions and │ 13 | │ limitations under the License. │ 14 | \*───────────────────────────────────────────────────────────────────────────*/ 15 | /* global require,module */ 16 | 17 | const fs = require('fs'); 18 | const debug = require('debug'); 19 | const log = debug('nemo-core:log'); 20 | const error = debug('nemo-core:error'); 21 | 22 | log.log = console.log.bind(console); 23 | error.log = console.error.bind(console); 24 | 25 | function Driver() { 26 | log('new Setup instance created'); 27 | 28 | return { 29 | setup: function doSetup(driverProps, callback) { 30 | log('entering doSetup'); 31 | 32 | let webdriver = require('selenium-webdriver'), 33 | SeleniumServer = require('selenium-webdriver/remote').SeleniumServer, 34 | proxy = require('selenium-webdriver/proxy'), 35 | caps, 36 | driver, 37 | tgtBrowser = driverProps.browser, 38 | localServer = driverProps.local || false, 39 | customCaps = driverProps.serverCaps, 40 | serverUrl = driverProps.server, 41 | serverProps = driverProps.serverProps || {}, 42 | serverJar = driverProps.jar, 43 | builders = driverProps.builders, 44 | proxyDetails = driverProps.proxyDetails, 45 | errorObject = null; 46 | 47 | function getServer() { 48 | log('attempt getServer'); 49 | //are we running the tests on the local machine? 50 | if (localServer === true) { 51 | log('test locally'); 52 | if (tgtBrowser !== 'chrome' && tgtBrowser !== 'phantomjs' && tgtBrowser !== 'firefox') { 53 | //make sure there is a jar file 54 | var jarExists = fs.existsSync(serverJar); 55 | if (!jarExists) { 56 | error('You must specify a valid SELENIUM_JAR value. The value must point to a driver executable in your file system.'); 57 | } 58 | if (serverProps.port === undefined) { 59 | serverProps.port = 4444; 60 | } 61 | var server = new SeleniumServer(serverJar, serverProps); 62 | server.start(); 63 | serverUrl = server.address(); 64 | } else { 65 | serverUrl = null; 66 | } 67 | } 68 | return serverUrl; 69 | } 70 | 71 | function getCapabilities() { 72 | //specified valid webdriver browser key? 73 | if (!webdriver.Capabilities[tgtBrowser]) { 74 | log('You have specified targetBrowser: ' + tgtBrowser + ' which is not a built-in webdriver.Capabilities browser option'); 75 | caps = new webdriver.Capabilities(); 76 | 77 | } else { 78 | caps = webdriver.Capabilities[tgtBrowser](); 79 | } 80 | if (customCaps) { 81 | Object.keys(customCaps).forEach(function customCapsKeys(key) { 82 | caps.set(key, customCaps[key]); 83 | }); 84 | } 85 | log('Capabilities', caps); 86 | return caps; 87 | } 88 | 89 | function getProxy() { 90 | if (proxyDetails) { 91 | log('proxyDetails', proxyDetails); 92 | if (proxyDetails.method && proxy[proxyDetails.method]) { 93 | return proxy[proxyDetails.method].apply(proxy, proxyDetails.args); 94 | } else { 95 | throw new Error('nemo: proxy configuration is incomplete or does not match the selenium-webdriver/proxy API'); 96 | } 97 | 98 | } else { 99 | return proxy.direct(); 100 | } 101 | } 102 | 103 | if (typeof driverProps == 'function') { 104 | driver = driverProps(); 105 | } else { 106 | try { 107 | var builder = new webdriver.Builder(); 108 | if (builders !== undefined) { 109 | Object.keys(builders).forEach(function (bldr) { 110 | builder = builder[bldr].apply(builder, builders[bldr]); 111 | }); 112 | } 113 | if (serverUrl !== undefined && !(builders && builders.usingServer)) { 114 | builder = builder.usingServer(getServer()); 115 | } 116 | if (tgtBrowser !== undefined && !(builders && builders.forBrowser)) { 117 | builder = builder.withCapabilities(getCapabilities()); 118 | } 119 | if (proxyDetails !== undefined) { 120 | builder = builder.setProxy(getProxy()); 121 | } 122 | log('builder FINAL', builder); 123 | driver = builder.build(); 124 | } catch (err) { 125 | error('Encountered an error during driver setup: %s', err); 126 | errorObject = err; 127 | callback(errorObject); 128 | return; 129 | } 130 | } 131 | 132 | driver.getSession().then(function () { 133 | callback(null, driver); 134 | }).catch(function (err) { 135 | error('Encountered an error during driver getSession: %s', err); 136 | callback(err); 137 | }); 138 | } 139 | }; 140 | } 141 | 142 | module.exports = Driver; 143 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Nemo-core [![Build Status](https://travis-ci.org/paypal/nemo-core.svg)](https://travis-ci.org/paypal/nemo-core) 2 | 3 | [![Join the chat at https://gitter.im/paypal/nemo-core](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/paypal/nemo-core?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) 4 | [![JS.ORG](https://img.shields.io/badge/js.org-nemo-ffb400.svg?style=flat-square)](http://js.org) 5 | [![Dependency Status](https://david-dm.org/paypal/nemo-core.svg)](https://david-dm.org/paypal/nemo-core) 6 | [![devDependency Status](https://david-dm.org/paypal/nemo-core/dev-status.svg)](https://david-dm.org/paypal/nemo-core#info=devDependencies) 7 | 8 | Nemo-core provides a simple way to add selenium automation to your NodeJS web projects. With a powerful configuration 9 | ability provided by [krakenjs/confit](https://github.com/krakenjs/confit), and plugin 10 | architecture, Nemo-core is flexible enough to handle any browser/device automation need. 11 | 12 | Nemo-core is built to easily plug into any task runner and test runner. But in this README we will only cover setup and architecture of Nemo-core 13 | as a standalone entity. 14 | 15 | ## Getting started 16 | 17 | ### Install 18 | 19 | ``` 20 | npm install --save-dev nemo-core 21 | ``` 22 | 23 | ### Pre-requisites 24 | 25 | #### Webdriver 26 | 27 | [Please see here for more information about setting up a webdriver](https://github.com/paypal/nemo-docs/blob/master/driver-setup.md). 28 | As long as you have the appropriate browser or browser driver (selenium-standalone, chromedriver) on your PATH, the rest of this should work 29 | fine. 30 | 31 | 32 | 33 | ### Nemo-core and Confit in 90 seconds 34 | 35 | Nemo-core uses confit - a powerful, expressive and intuitive configuration system - to elegantly expose the Nemo-core and selenium-webdriver APIs. 36 | 37 | #### Direct configuration 38 | 39 | If you install this repo you'll get the following in `examples/setup.js`. Note the `NemoCore()` constructor is directly accepting the needed configuration, 40 | along with a callback function. 41 | 42 | ```javascript 43 | NemoCore({ 44 | "driver": { 45 | "browser": "firefox" 46 | }, 47 | 'data': { 48 | 'baseUrl': 'https://www.paypal.com' 49 | } 50 | }, function (err, nemo) { 51 | //always check for errors! 52 | if (!!err) { 53 | console.log('Error during Nemo-core setup', err); 54 | } 55 | nemo.driver.get(nemo.data.baseUrl); 56 | nemo.driver.getCapabilities(). 57 | then(function (caps) { 58 | console.info("Nemo-core successfully launched", caps.caps_.browserName); 59 | }); 60 | nemo.driver.quit(); 61 | }); 62 | ``` 63 | 64 | ##### you can use async/await as well 65 | ```javascript 66 | const Nemo = require('nemo-core'); 67 | (async () => { 68 | const nemo = await Nemo({ 69 | driver: { 70 | browser: 'firefox' 71 | }, 72 | data: { 73 | baseUrl: 'https://www.paypal.com' 74 | } 75 | }); 76 | await nemo.driver.get(nemo.data.baseUrl); 77 | const caps = await nemo.driver.getCapabilities(); 78 | console.log(`nemo-core successfully launched with ${caps.get('browserName')}`); 79 | await nemo.driver.quit(); 80 | })(); 81 | ``` 82 | 83 | ##### make sure to handle errors when using async/await 84 | You can modify the example above to handle errors. Make sure to define nemo outside of your try block, so you can "quit" the driver if an error occurs after nemo has been successfully created 85 | ```javascript 86 | const Nemo = require('nemo-core'); 87 | let nemo; 88 | (async () => { 89 | try { 90 | nemo = await Nemo({ 91 | driver: { 92 | browser: 'firefox' 93 | }, 94 | data: { 95 | baseUrl: 'https://www.paypal.com' 96 | } 97 | }); 98 | await nemo.driver.get(nemo.data.baseUrl); 99 | throw new Error('artificial error to test error handling!'); 100 | nemo.driver.quit(); // this is not called, but we can call it in our catch block 101 | } catch (err) { 102 | console.log(err); 103 | // attempt to quit the driver in error scenarios 104 | if (nemo) { 105 | nemo.driver.quit(); 106 | } 107 | } 108 | })(); 109 | ``` 110 | 111 | Run it: 112 | 113 | ```bash 114 | $ node examples/setup.js 115 | Nemo-core successfully launched firefox 116 | ``` 117 | 118 | #### Using config files 119 | 120 | Look at `examples/setupWithConfigFiles.js` 121 | 122 | ```javascript 123 | //passing __dirname as the first argument tells confit to 124 | //look in __dirname + '/config' for config files 125 | NemoCore(__dirname, function (err, nemo) { 126 | //always check for errors! 127 | if (!!err) { 128 | console.log('Error during Nemo-core setup', err); 129 | } 130 | 131 | nemo.driver.get(nemo.data.baseUrl); 132 | nemo.driver.getCapabilities(). 133 | then(function (caps) { 134 | console.info("Nemo-core successfully launched", caps.caps_.browserName); 135 | }); 136 | nemo.driver.quit(); 137 | }); 138 | ``` 139 | 140 | Note the comment above that passing a filesystem path as the first argument to `NemoCore()` will tell confit to look in that directory + `/config` for config files. 141 | 142 | Look at `examples/config/config.json` 143 | 144 | ```javascript 145 | { 146 | "driver": { 147 | "browser": "config:BROWSER" 148 | }, 149 | "data": { 150 | "baseUrl": "https://www.paypal.com" 151 | }, 152 | "BROWSER": "firefox" 153 | } 154 | ``` 155 | 156 | That is almost the same config as the first example. But notice `"config:BROWSER"`. Yes, confit will resolve that to the config property `"BROWSER"`. 157 | 158 | Run this and it will open the Firefox browser: 159 | 160 | ```bash 161 | $ node examples/setup.js 162 | Nemo-core successfully launched firefox 163 | ``` 164 | 165 | Now run this command: 166 | 167 | ```bash 168 | $ node examples/setupWithConfigDir.js --BROWSER=chrome 169 | Nemo-core successfully launched chrome 170 | ``` 171 | 172 | Here, confit resolves the `--BROWSER=chrome` command line argument and overrides the `BROWSER` value from config.json 173 | 174 | Now this command: 175 | 176 | ```bash 177 | $ BROWSER=chrome node examples/setupWithConfigDir.js 178 | Nemo-core successfully launched chrome 179 | ``` 180 | 181 | Here, confit resolves the `BROWSER` environment variable and overrides `BROWSER` from config.json 182 | 183 | What if we set both? 184 | 185 | ```bash 186 | $ BROWSER=chrome node examples/setupWithConfigDir.js --BROWSER=phantomjs 187 | Nemo-core successfully launched chrome 188 | ``` 189 | 190 | You can see that the environment variable wins. 191 | 192 | Now try this command: 193 | 194 | ``` 195 | $ NODE_ENV=special node examples/setupWithConfigDir.js 196 | Nemo-core successfully launched phantomjs 197 | ``` 198 | 199 | Note that confit uses the value of NODE_ENV to look for an override config file. In this case `config/special.json`: 200 | 201 | ```javascript 202 | { 203 | "driver": { 204 | "browser": "phantomjs" 205 | }, 206 | "data": { 207 | "baseUrl": "https://www.paypal.com" 208 | } 209 | } 210 | ``` 211 | 212 | Hopefully this was an instructive dive into the possibilities of Nemo-core + confit. There is more to learn but hopefully this is enough to whet your appetite for now! 213 | 214 | ## Nemo-core and Plugins in 60 Seconds 215 | 216 | Look at the `example/setupWithPlugin.js` file: 217 | 218 | ```javascript 219 | NemoCore(basedir, function (err, nemo) { 220 | //always check for errors! 221 | if (!!err) { 222 | console.log('Error during Nemo-core setup', err); 223 | } 224 | nemo.driver.getCapabilities(). 225 | then(function (caps) { 226 | console.info("Nemo-core successfully launched", caps.caps_.browserName); 227 | }); 228 | nemo.driver.get(nemo.data.baseUrl); 229 | nemo.cookie.deleteAll(); 230 | nemo.cookie.set('foo', 'bar'); 231 | nemo.cookie.getAll().then(function (cookies) { 232 | console.log('cookies', cookies); 233 | console.log('======================='); 234 | }); 235 | nemo.cookie.deleteAll(); 236 | nemo.cookie.getAll().then(function (cookies) { 237 | console.log('cookies', cookies); 238 | }); 239 | nemo.driver.quit(); 240 | }); 241 | ``` 242 | 243 | Notice the `nemo.cookie` namespace. This is actually a plugin, and if you look at the config for this setup: 244 | ```javascript 245 | { 246 | "driver": { 247 | "browser": "firefox" 248 | }, 249 | "data": { 250 | "baseUrl": "https://www.paypal.com" 251 | }, 252 | "plugins": { 253 | "cookie": { 254 | "module": "path:./nemo-cookie" 255 | } 256 | } 257 | } 258 | ``` 259 | You'll see the `plugins.cookie` section, which is loading `examples/plugin/nemo-cookie.js` as a plugin: 260 | ```javascript 261 | 'use strict'; 262 | 263 | module.exports = { 264 | "setup": function (nemo, callback) { 265 | nemo.cookie = {}; 266 | nemo.cookie.delete = function (name) { 267 | return nemo.driver.manage().deleteCookie(name); 268 | }; 269 | nemo.cookie.deleteAll = function () { 270 | return nemo.driver.manage().deleteAllCookies(); 271 | }; 272 | nemo.cookie.set = function (name, value, path, domain, isSecure, expiry) { 273 | return nemo.driver.manage().addCookie(name, value, path, domain, isSecure, expiry) 274 | }; 275 | nemo.cookie.get = function (name) { 276 | return nemo.driver.manage().getCookie(name); 277 | }; 278 | nemo.cookie.getAll = function () { 279 | return nemo.driver.manage().getCookies(); 280 | }; 281 | callback(null); 282 | 283 | } 284 | }; 285 | ``` 286 | 287 | Running this example: 288 | 289 | ```bash 290 | $ node examples/setupWithPlugin.js 291 | Nemo-core successfully launched firefox 292 | cookies [ { name: 'foo', 293 | value: 'bar', 294 | path: '', 295 | domain: 'www.paypal.com', 296 | secure: false, 297 | expiry: null } ] 298 | ======================= 299 | cookies [] 300 | $ 301 | ``` 302 | 303 | This illustrates how you can create a plugin, and the sorts of things you might want to do with a plugin. 304 | 305 | ## API 306 | 307 | ### Nemo-core 308 | 309 | `var nemo = NemoCore([[nemoBaseDir, ][config, ][callback]] | [Confit object]);` 310 | 311 | `@argument nemoBaseDir {String}` (optional) - If provided, should be a filesystem path to your test suite. Nemo-core will expect to find a `/config` directory beneath that. 312 | `/config/config.json` should have your default configuration (described below). `nemoBaseDir` can alternatively be set as an environment variable. If it is 313 | not set, you need to pass your configuration as the `config` parameter (see below). 314 | 315 | `@argument config {Object}` (optional) - Can be a full configuration (if `nemoBaseDir` not provided) or additional/override configuration to what's in your config files. 316 | 317 | `@argument callback {Function}` (optional) - This function will be called once the `nemo` object is fully resolved. It may be called with an error as the first argument which has important debugging information. So make sure to check for an error. The second argument is the resolved `nemo` object. 318 | 319 | `@argument Confit object {Object}` (optional) - If a Confit object is passed, the configuration step is skipped and the passed object is used directly. 320 | 321 | `@returns nemo {Object|Promise}` - Promise returned if no callback provided. Promise resolves with the same nemo object as would be given to the callback. 322 | The nemo object has the following properties: 323 | 324 | * **driver** The live `selenium-webdriver` API. See WebDriver API Doc: http://seleniumhq.github.io/selenium/docs/api/javascript/module/selenium-webdriver/index_exports_WebDriver.html 325 | 326 | * **wd** This is a reference to the `selenium-webdriver` module: http://seleniumhq.github.io/selenium/docs/api/javascript/module/selenium-webdriver/index.html 327 | 328 | * **data** Pass through of any data included in the `data` property of the configuration 329 | 330 | * **_config** This is the confit configuration object. Documented here: https://github.com/krakenjs/confit#config-api 331 | 332 | * **plugin namespace(s)** Plugins are responsible for registering their own namespace under nemo. The convention is that the plugin should register the same namespace 333 | as is specified in the plugin configuration. E.g. the `nemo-view` module registers itself as `nemo.view` and is configured like this: 334 | 335 | ```javascript 336 | { 337 | "driver": ... 338 | "plugins": { 339 | "view": { 340 | "module": "nemo-view" 341 | } 342 | } 343 | ``` 344 | 345 | You could also have a config that looks like this, and `nemo-view` will still register itself as `nemo.view` 346 | 347 | ```javascript 348 | { 349 | "driver": ... 350 | "plugins": { 351 | "cupcakes": { 352 | "module": "nemo-view" 353 | } 354 | } 355 | ``` 356 | 357 | But that's confusing. So please stick to the convention. 358 | 359 | #### Typical usage of Nemo-core constructor 360 | 361 | A typical pattern would be to use `mocha` as a test runner, resolve `nemo` in the context of the mocha `before` function, and use 362 | the mocha `done` function as the callback: 363 | 364 | ```javascript 365 | var nemo; 366 | describe('my nemo suite', function () { 367 | before(function (done) { 368 | NemoCore(config, function (err, resolvedNemo-core) { 369 | nemo = resolvedNemo-core; 370 | done(err) 371 | }); 372 | }); 373 | it('will launch browsers!', function (done) { 374 | nemo.driver.get('https://www.paypal.com'); 375 | nemo.driver.quit().then(function () { 376 | done(); 377 | }); 378 | }); 379 | }); 380 | 381 | ``` 382 | 383 | ### Configure 384 | 385 | Calling `Configure` will return a promise which resolves as a Confit object. This is the same method Nemo-core calls internally in the basic use case. You might want to call `Configure` if 386 | you are interested in the resolved configuration object but not yet ready to start the webdriver. An example would be if you want to make further 387 | changes to the configuration based on what gets resolved, prior to starting the webdriver. 388 | 389 | `function Configure([nemoBaseDir, ][configOverride])` 390 | 391 | `@argument nemoBaseDir {String}` (optional) - If provided, should be a filesystem path. There should be a `/config` directory beneath that. 392 | `/config/config.json` should have your default configuration. `nemoBaseDir` can alternatively be set as an environment variable. If it is 393 | not set, you need to pass your configuration as the `config` parameter (see below). 394 | 395 | `@argument config {Object}` (optional) - Can be a full configuration (if `nemoBaseDir` not provided) or additional/override configuration to what's in your config files. 396 | 397 | `@returns {Promise}` - Promise resolves as a confit object: 398 | 399 | 400 | ## Configuration Input 401 | 402 | ```javascript 403 | { 404 | "driver": { /** properties used by Nemo-core to setup the driver instance **/ }, 405 | "plugins": { /** plugins to initialize **/}, 406 | "data": { /** arbitrary data to pass through to nemo instance **/ } 407 | } 408 | ``` 409 | 410 | This configuration object is optional, as long as you've got `nemoData` set as an environment variable (see below). 411 | 412 | ### driver 413 | 414 | Here are the `driver` properties recognized by Nemo-core. This is ALL of them. Please be aware that you really only need to supply "browser" to get things working initially. 415 | 416 | #### browser (optional) 417 | 418 | Browser you wish to automate. Make sure that your chosen webdriver has this browser option available. While this is "optional" you must choose a browser. Either use this property or the `builders.forBrowser` option (see below). 419 | If both are specified, `builders.forBrowser` takes precedence. 420 | 421 | #### local (optional, defaults to false) 422 | 423 | Set local to true if you want Nemo-core to attempt to start a standalone binary on your system (like selenium-standalone-server) or use a local browser/driver like Chrome/chromedriver or PhantomJS. 424 | 425 | #### server (optional) 426 | 427 | Webdriver server URL you wish to use. This setting will be overridden if you are using `builders.usingServer` 428 | 429 | #### serverProps (optional/conditional) 430 | 431 | Additional server properties required of the 'targetServer' 432 | 433 | You can also set args and jvmArgs to the selenium jar process as follows: 434 | 435 | ```javascript 436 | "serverProps": { 437 | "port": 4444, 438 | "args": ["-firefoxProfileTemplate","/Users/medelman/Desktop/ffprofiles"], 439 | "jvmArgs": ["-someJvmArg", "someJvmArgValue"] 440 | } 441 | ``` 442 | 443 | #### jar (optional/conditional) 444 | 445 | Path to your webdriver server Jar file. Leave unset if you aren't using a local selenium-standalone Jar (or similar). 446 | 447 | #### serverCaps (optional) 448 | 449 | serverCaps would map to the capabilities here: http://seleniumhq.github.io/selenium/docs/api/javascript/module/selenium-webdriver/index_exports_Capabilities.html 450 | 451 | Some webdrivers (for instance ios-driver, or appium) would have additional capabilities which can be set via this variable. As an example, you can connect to saucelabs by adding this serverCaps: 452 | 453 | ```javascript 454 | "serverCaps": { 455 | "username": "medelman", 456 | "accessKey": "b38e179e-079a-417d-beb8-xyz", //not my real access key 457 | "name": "Test Suite Name", //sauce labs session name 458 | "tags": ['tag1','tag2'] //sauce labs tag names 459 | } 460 | ``` 461 | #### proxyDetails (optional) 462 | If you want to run test by setting proxy in the browser, you can use 'proxyDetails' configuration. Following options are available: direct, manual, pac and system. 463 | Default is 'direct'. For more information refer : http://seleniumhq.github.io/selenium/docs/api/javascript/module/selenium-webdriver/proxy.html 464 | 465 | ```javascript 466 | "proxyDetails" : { 467 | "method": "manual", 468 | "args": [{"http": "localhost:9001","ftp":"localhost:9001","https":"localhost:9001"}] 469 | } 470 | ``` 471 | 472 | #### builders (optional) 473 | 474 | This is a JSON interface to any of the Builder methods which take simple arguments and return the builder. See the Builder class here: http://seleniumhq.github.io/selenium/docs/api/javascript/module/selenium-webdriver/index_exports_Builder.html 475 | 476 | Useful such functions are: 477 | * forBrowser (can take the place of "browser", "local" and "jar" properties above) 478 | * withCapabilities (can take the place of "serverCaps" above) 479 | 480 | The nemo setup routine will prefer these "builder" properties over other abstracted properties above, if there is a conflict. 481 | 482 | #### selenium.version (optional) 483 | 484 | Since nemo requires a narrow range of versions of selenium-webdriver, you may have a need to upgrade selenium-webdriver (or downgrade) outside of the supported versions that nemo uses. 485 | You can do that by using `selenium.version`. E.g. 486 | 487 | ```js 488 | "driver": { 489 | "browser": "firefox", 490 | "selenium.version": "^2.53.1" 491 | } 492 | ``` 493 | 494 | Nemo-core will upgrade its internal dependency to what is set in this property. The `npm install` will only run if the version specified is not already installed. 495 | 496 | #### custom driver 497 | 498 | You can also provide a module, which exports a function that returns a fully formed `WebDriver` object. To do so, follow 499 | this example: https://github.com/paypal/nemo-core/pull/177#issue-199917000 500 | 501 | ### plugins 502 | 503 | Plugins are registered with JSON like the following (will vary based on your plugins) 504 | 505 | ```javascript 506 | { 507 | "plugins": { 508 | "samplePlugin": { 509 | "module": "path:plugin/sample-plugin", 510 | "arguments": [...], 511 | "priority": 99 512 | }, 513 | "view": { 514 | "module": "nemo-view" 515 | } 516 | } 517 | } 518 | ``` 519 | 520 | Plugin.pluginName parameters: 521 | 522 | * **module {String}** - Module must resolve to a require'able module, either via name (in the case it is in your dependency tree) or via path to the file or directory. 523 | 524 | * **arguments {Array}** (optional, depending on plugin) - Your plugin will be called via its setup method with these arguments: `[configArg1, configArg2, ..., ]nemo, callback`. 525 | Please note that the braces there indicate "optional". The arguments will be applied via `Function.apply` 526 | 527 | * **priority {Number}** (optional, depending on plugin) - A `priority` value of < 100 will register this plugin BEFORE the selenium driver object is created. 528 | This means that such a plugin can modify `config` properties prior to driver setup. Leaving `priority` unset will register the plugin after the driver object is created. 529 | 530 | ### data 531 | 532 | Data will be arbitrary stuff that you might like to use in your tests. In a lot of the examples we set `data.baseUrl` but again, that's arbitrary and not required. You could 533 | pass and use `data.cupcakes` if you want. Cupcakes are awesome. 534 | 535 | ## Shortstop handlers 536 | 537 | Shortstop handlers are data processors that key off of directives in the JSON data. Ones that are enabled in nemo are: 538 | 539 | ### path 540 | 541 | use path to prepend the `nemoBaseDir` (or `process.cwd()`) to a value. E.g. if `nemoBaseDir` is `.../myApp/tests` then 542 | a config value of `'path:plugin/myPlugin'` will resolve to `.../myApp/tests/plugin/myPlugin` 543 | 544 | ### env 545 | 546 | use env to reference environment variables. E.g. a config value of `'env:PATH'` will resolve to 547 | `/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:...` 548 | 549 | ### argv 550 | 551 | use argv to reference argv variables. E.g. a config value of `'argv:browser'` will resolve to `firefox` if you 552 | set `--browser firefox` as an argv on the command line 553 | 554 | ### config 555 | 556 | Use config to reference data in other parts of the JSON configuration. E.g. in the following config.json: 557 | 558 | ```javascript 559 | { 560 | "driver": { 561 | ... 562 | }, 563 | "plugins": { 564 | "myPlugin": { 565 | "module": "nemo-some-plugin", 566 | "arguments": ["config:data.someProp"] 567 | } 568 | }, 569 | "data": { 570 | "someProp": "someVal" 571 | } 572 | } 573 | ``` 574 | 575 | The value of `plugins.myPlugin.arguments[0]` will be `someVal` 576 | 577 | 578 | ### file 579 | 580 | Please see [https://github.com/krakenjs/shortstop-handlers#handlersfilebasedir-options](https://github.com/krakenjs/shortstop-handlers#handlersfilebasedir-options) 581 | 582 | ### base64 583 | 584 | Please see [https://github.com/krakenjs/shortstop-handlers#handlersbase64](https://github.com/krakenjs/shortstop-handlers#handlersbase64) 585 | 586 | ### require 587 | 588 | Please see [https://github.com/krakenjs/shortstop-handlers#handlersrequirebasedir](https://github.com/krakenjs/shortstop-handlers#handlersrequirebasedir) 589 | 590 | ### exec 591 | 592 | Please see [https://github.com/krakenjs/shortstop-handlers#handlersexecbasedir](https://github.com/krakenjs/shortstop-handlers#handlersexecbasedir) 593 | 594 | ### glob 595 | 596 | Please see [https://github.com/krakenjs/shortstop-handlers#handlersglobbasediroptions](https://github.com/krakenjs/shortstop-handlers#handlersglobbasediroptions) 597 | 598 | ## Plugins 599 | 600 | Authoring a plugin, or using an existing plugin, is a great way to increase the power and usefulness of your Nemo-core installation. A plugin should add 601 | its API to the `nemo` object it receives and passes on in its constructor (see "plugin interface" below) 602 | 603 | ### plugin interface 604 | 605 | A plugin should export a setup function with the following interface: 606 | 607 | ```javascript 608 | module.exports.setup = function myPlugin([arg1, arg2, ..., ]nemo, callback) { 609 | ... 610 | //add your plugin to the nemo namespace 611 | nemo.myPlugin = myPluginFactory([arg1, arg2, ...]); //adds myMethod1, myMethod2 612 | 613 | //error in your plugin setup 614 | if (err) { 615 | callback(err); 616 | return; 617 | } 618 | //continue 619 | callback(null); 620 | }; 621 | ``` 622 | 623 | When nemo initializes your plugin, it will call the setup method with any arguments supplied in the config plus the nemo object, 624 | plus the callback function to continue plugin initialization. 625 | 626 | Then in your module where you use Nemo-core, you will be able to access the plugin functionality: 627 | 628 | ```javascript 629 | var Nemo-core = require('nemo'); 630 | NemoCore({ 631 | 'driver': { 632 | 'browser': 'firefox', 633 | 'local': true, 634 | 'jar': '/usr/local/bin/selenium-server-standalone.jar' 635 | }, 636 | 'data': { 637 | 'baseUrl': 'https://www.paypal.com' 638 | }, 639 | 'plugins': { 640 | 'myPlugin': { 641 | 'module': 'path:plugin/my-plugin', 642 | 'arguments': [...] 643 | 'priority': 99 644 | }, 645 | } 646 | }, function (err, nemo) { 647 | nemo.driver.get(nemo.data.baseUrl); 648 | nemo.myPlugin.myMethod1(); 649 | nemo.myPlugin.myMethod2(); 650 | nemo.driver.sleep(5000). 651 | then(function() { 652 | console.info('Nemo-core was successful!!'); 653 | nemo.driver.quit(); 654 | }); 655 | }); 656 | ``` 657 | 658 | 659 | 660 | 661 | ## Logging and debugging 662 | 663 | Nemo-core uses the [debug](https://github.com/visionmedia/debug.git) module for console logging and error output. There are two classes of logging, `nemo:log` and `nemo:error` 664 | 665 | If you want to see both classes of output, simply use the appropriate value of the DEBUG environment variable when you run nemo: 666 | 667 | ```bash 668 | $ DEBUG=nemo:* 669 | ``` 670 | 671 | To see just one: 672 | 673 | ```bash 674 | $ DEBUG=nemo:error 675 | ``` 676 | 677 | 678 | ## Why Nemo-core? 679 | 680 | Because we NEed MOre automation testing! 681 | 682 | [![NPM](https://nodei.co/npm/nemo.png?downloads=true&stars=true)](https://nodei.co/npm/nemo/) 683 | 684 | ## Unit Tests 685 | 686 | * Unit tests run by default using headless browser [PhantomJS](http://phantomjs.org/). To run unit tests out of box, You must have PhantomJS installed on your system and must be present in the path 687 | * Download PhantomJS from [here](http://phantomjs.org/download.html) 688 | * On OSX, you can optionally use `brew` to install PhantomJS like `brew install phantomjs` 689 | * PhantomJS installation detailed guide on Ubuntu can be found [here](https://gist.github.com/julionc/7476620) 690 | 691 | * If you want to run unit tests on your local browser, like lets say Firefox/Chrome (make sure ChromeDriver is in current path), you need to update browser in unit test 692 | configuration, for example the browser section under `test/config/config.json` like [here](https://github.com/paypal/nemo-core/blob/master/test/config/config.json#L19) 693 | 694 | * How to run unit tests? 695 | * `npm test` will run unit tests as well as lint task 696 | * `grunt simplemocha` will just run unit tests 697 | * `grunt` - default grunt task will run linting as well as unit tests 698 | * To run directly using mocha assuming its globally installed on your system `mocha -t 60s` 699 | * Or a specific test, `mocha --grep @allArgs@ -t 60s` 700 | * Or post `npm install` on nemo module, you can run `node_modules/.bin/mocha --grep @allArgs@ -t 60s` 701 | --------------------------------------------------------------------------------