fs = require('fs'); var builder = require('./builder'); var methodBuilder = builder.methodBuilder , callbackWithData = builder.callbackWithData , elementCallback = builder.elementCallback , elementsCallback = builder.elementsCallback , __slice = Array.prototype.slice; var protocol = {}; protocol.init = builder.init; protocol.status = methodBuilder({ method: 'GET' , absPath: function() { return this.basePath + '/status' } , cb: function(cb) { return callbackWithData(cb); } }); protocol.sessions = methodBuilder({ method: 'GET' , absPath: function() { return this.basePath + '/sessions' } , cb: function(cb) { return callbackWithData(cb); } }); // alternate strategy to get session capabilities // extract session capabilities from session list protocol.altSessionCapabilities = function(cb) { var _this = this; // looking for the current session protocol.sessions.apply(this, [function(err, sessions) { if (err == null) { sessions = sessions.filter(function(session) { return session.id === _this.sessionID; }); var _ref; return cb(null, (_ref = sessions[0]) != null ? _ref.capabilities : void 0); } else { return cb(err, sessions); } }]); }; protocol.sessionCapabilities = methodBuilder({ method: 'GET' // default url , cb: function(cb) { return callbackWithData(cb); } }); protocol.close = methodBuilder({ method: 'DELETE' , relPath: '/window' }); protocol.quit = methodBuilder({ method: 'DELETE' // default url , emit: {event: 'status', message: '\nEnding your web drivage..\n'} }); protocol.eval = function(code, cb) { code = "return " + code + ";" protocol.execute.apply( this, [code, function(err, res) { if(err!=null) {return cb(err);} cb(null, res); }]); }; protocol.safeEval = function(code, cb) { protocol.safeExecute.apply( this, [code, function(err, res) { if(err!=null) {return cb(err);} cb(null, res); }]); }; protocol.execute = methodBuilder({ method: 'POST' , relPath: '/execute' , cb: function() { // parsing args, cb at the end var cb, _args, _i; _args = 2 <= arguments.length ? __slice.call(arguments, 0, _i = arguments.length - 1) : (_i = 0, []), cb = arguments[_i++]; return callbackWithData(cb); } , data: function() { // parsing arguments (code,args,cb) with optional args var args, cb, code, _args, _i; _args = 2 <= arguments.length ? __slice.call(arguments, 0, _i = arguments.length - 1) : (_i = 0, []), cb = arguments[_i++]; code = _args[0], args = _args[1]; //args default if (typeof args === "undefined" || args === null) { args = []; } return {script: code, args: args}; } }); // script to be executed in browser var safeExecuteJsScript = fs.readFileSync( __dirname + "/browser-scripts/safe-execute.js", 'utf8'); protocol.safeExecute = methodBuilder({ method: 'POST' , relPath: '/execute' , cb: function() { // parsing args, cb at the end var cb, _args, _i; _args = 2 <= arguments.length ? __slice.call(arguments, 0, _i = arguments.length - 1) : (_i = 0, []), cb = arguments[_i++]; return callbackWithData(cb); } , data: function() { // parsing arguments (code,args,cb) with optional args var args, cb, code, _args, _i; _args = 2 <= arguments.length ? __slice.call(arguments, 0, _i = arguments.length - 1) : (_i = 0, []), cb = arguments[_i++]; code = _args[0], args = _args[1]; //args default if (typeof args === "undefined" || args === null) { args = []; } return {script: safeExecuteJsScript, args: [code, args]}; } }); protocol.executeAsync = methodBuilder({ method: 'POST' , relPath: '/execute_async' , cb: function() { // parsing args, cb at the end var cb, _args, _i; _args = 2 <= arguments.length ? __slice.call(arguments, 0, _i = arguments.length - 1) : (_i = 0, []), cb = arguments[_i++]; return callbackWithData(cb); } , data: function(code) { // parsing arguments (code,args,cb) with optional args var args, cb, code, _args, _i; _args = 2 <= arguments.length ? __slice.call(arguments, 0, _i = arguments.length - 1) : (_i = 0, []), cb = arguments[_i++]; code = _args[0], args = _args[1]; //args default if (typeof args === "undefined" || args === null) { args = []; } return {script: code, args: args}; } }); // script to be executed in browser var safeExecuteAsyncJsScript = fs.readFileSync( __dirname + "/browser-scripts/safe-execute-async.js", 'utf8'); protocol.safeExecuteAsync = methodBuilder({ method: 'POST' , relPath: '/execute_async' , cb: function() { // parsing args, cb at the end var cb, _args, _i; _args = 2 <= arguments.length ? __slice.call(arguments, 0, _i = arguments.length - 1) : (_i = 0, []), cb = arguments[_i++]; return callbackWithData(cb); } , data: function(code) { // parsing arguments (code,args,cb) with optional args var args, cb, code, _args, _i; _args = 2 <= arguments.length ? __slice.call(arguments, 0, _i = arguments.length - 1) : (_i = 0, []), cb = arguments[_i++]; code = _args[0], args = _args[1]; //args default if (typeof args === "undefined" || args === null) { args = []; } return {script: safeExecuteAsyncJsScript , args: [code, args]}; } }); protocol.get = methodBuilder({ method: 'POST' , relPath: '/url' , data: function(url) { return {'url': url}; } }); protocol.refresh = methodBuilder({ method: 'POST' , relPath: '/refresh' }); protocol.forward = methodBuilder({ method: 'POST' , relPath: '/forward' }); protocol.back = methodBuilder({ method: 'POST' , relPath: '/back' }); protocol.setImplicitWaitTimeout = methodBuilder({ method: 'POST' , relPath: '/timeouts/implicit_wait' , data: function(ms) { return {ms: ms}; } }); // for backward compatibility protocol.setWaitTimeout = protocol.setImplicitWaitTimeout; protocol.setAsyncScriptTimeout = methodBuilder({ method: 'POST' , relPath: '/timeouts/async_script' , data: function(ms) { return {ms: ms}; } }); protocol.setPageLoadTimeout = methodBuilder({ method: 'POST' , relPath: '/timeouts/timeouts' , data: function(ms) { return {type: 'page load', ms: ms}; } }); protocol.element = methodBuilder({ method: 'POST' , relPath: '/element' , cb: function(using, value, cb) { return elementCallback(cb); } , data: function(using, value) { return {using: using, value: value}; } }); // avoid not found exception and return null instead protocol.elementOrNull = function(using, value, cb) { protocol.elements.apply(this, [using, value, function(err, elements) { if(err == null) if(elements.length>0) {cb(null,elements[0]);} else {cb(null,null);} else cb(err); } ]); }; // avoid not found exception and return undefined instead protocol.elementIfExists = function(using, value, cb) { protocol.elements.apply(this, [using, value, function(err, elements) { if(err == null) if(elements.length>0) {cb(null,elements[0]);} else {cb(null,undefined);} else cb(err); } ]); }; protocol.elements = methodBuilder({ method: 'POST' , relPath: '/elements' , cb: function(using, value, cb) { return elementsCallback(cb); } , data: function(using, value) { return {using: using, value: value}; } }); protocol.hasElement = function(using, value, cb){ protocol.elements.apply( this, [using, value, function(err, elements){ if(err==null) cb(null, elements.length > 0 ) else cb(err); }]); } // convert to type to something like ById, ByCssSelector, etc... var elFuncSuffix = function(type){ res = (' by ' + type).replace(/(\s[a-z])/g, function($1){return $1.toUpperCase().replace(' ','');}); return res.replace('Xpath', 'XPath'); }; // return correct jsonwire type var elFuncFullType = function(type){ if(type == 'css') {return 'css selector'} // shortcut for css return type; }; // from JsonWire spec + shortcuts var elementFuncTypes = ['class name', 'css selector','id','name','link text', 'partial link text','tag name', 'xpath', 'css' ]; // adding all elementBy... , elementsBy... function for (var i = 0; i < elementFuncTypes.length; i++) { (function() { var type = elementFuncTypes[i]; protocol['element' + elFuncSuffix(type)] = function(value, cb) { protocol.element.apply(this, [elFuncFullType(type), value, cb]); }; // avoid not found exception and return null instead protocol['element' + elFuncSuffix(type)+ 'OrNull'] = function(value, cb) { protocol.elements.apply(this, [elFuncFullType(type), value, function(err, elements) { if(err == null) if(elements.length>0) {cb(null,elements[0]);} else {cb(null,null);} else cb(err); } ]); }; // avoid not found exception and return undefined instead protocol['element' + elFuncSuffix(type)+ 'IfExists'] = function(value, cb) { protocol.elements.apply(this, [elFuncFullType(type), value, function(err, elements) { if(err == null) if(elements.length>0) {cb(null,elements[0]);} else {cb(null,undefined);} else cb(err); } ]); }; protocol['hasElement' + elFuncSuffix(type)] = function(value, cb) { protocol.hasElement.apply(this, [elFuncFullType(type), value, cb]); }; protocol['elements' + elFuncSuffix(type)] = function(value, cb) { protocol.elements.apply(this, [elFuncFullType(type), value, cb]); }; })(); } protocol.getAttribute = methodBuilder({ method: 'GET' , relPath: function(element, attrName) { return '/element/' + element + '/attribute/' + attrName; } , cb: function(element, attrName, cb) { return callbackWithData(cb); } }); protocol.getValue = function(element, cb) { protocol.getAttribute.apply(this, [element, 'value', cb]); }; protocol.clickElement = methodBuilder({ method: 'POST' , relPath: function(element, attrName) { return '/element/' + element + '/click'; } }); protocol.moveTo = methodBuilder({ method: 'POST' , relPath: '/moveto' , data: function(element, xoffset, yoffset) { return { element: element, xoffset: xoffset, yoffset: yoffset }; } }); //@todo simulate the scroll event using dispatchEvent and browser.execute /* it's not implemented so taking it out protocol.scroll = methodBuilder({ method: 'POST' , relPath:'/moveto' , data: function(element, xoffset, yoffset) { return { element : element, xoffset : xoffset, yoffset : yoffset }; } }); */ protocol.buttonDown = methodBuilder({ method: 'POST' , relPath: '/buttondown' }); protocol.buttonUp = methodBuilder({ method: 'POST' , relPath: '/buttonup' }); //{LEFT = 0, MIDDLE = 1 , RIGHT = 2} protocol.click = methodBuilder({ method: 'POST' , relPath: '/click' , data: function(button) { return {button: button}; } }); protocol.doubleclick = methodBuilder({ method: 'POST' , relPath: '/doubleclick' }); //All keys are up at end of command protocol.type = methodBuilder({ method: 'POST' , relPath: function(element, keys) { return '/element/' + element + '/value'; } , data: function(element, keys) { if (!(keys instanceof Array)) {keys = [keys];} return {value: keys}; } }); protocol.keys = methodBuilder({ method: 'POST' , relPath: '/keys' , data: function(keys) { if (!(keys instanceof Array)) {keys = [keys];} return {value: keys}; } }); protocol.clear = methodBuilder({ method: 'POST' , relPath: function(element) { return '/element/' + element + '/clear'; } }); protocol.title = methodBuilder({ method: 'GET' , relPath: '/title' , cb: function(cb) { return callbackWithData(cb); } }); // element must be specified _rawText = methodBuilder({ method: 'GET' , relPath: function(element) { return '/element/' + element + '/text'; } , cb: function(element, cb) { return callbackWithData(cb); } }); // element is specific element, 'body', or undefined protocol.text = function(element, cb) { var _this = this; if (typeof element === 'undefined' || element == 'body' || element === null) { protocol.element.apply(this, ['tag name', 'body', function(err, bodyEl) { if (err == null) {_rawText.apply(_this, [bodyEl, cb]);} else {cb(err);} }]); }else { _rawText.apply(_this, [element, cb]); } }; // element is specific element, 'body', or undefined protocol.textPresent = function(searchText, element, cb) { protocol.text.apply(this, [element, function(err, text) { if (err) { cb(err, null); } else { cb(err, text.indexOf(searchText) >= 0); } }]); }; protocol.acceptAlert = methodBuilder({ method: 'POST' , relPath: '/accept_alert' }); protocol.dismissAlert = methodBuilder({ method: 'POST' , relPath: '/dismiss_alert' }); protocol.active = methodBuilder({ method: 'POST' , relPath: '/element/active' , cb: function(cb) { return callbackWithData(function(e, o) { cb(null, o['ELEMENT'])}); } }); protocol.url = methodBuilder({ method: 'GET' , relPath: '/url' , cb: function(cb) { return callbackWithData(cb); } }); protocol.allCookies = methodBuilder({ method: 'GET' , relPath: '/cookie' , cb: function(cb) { return callbackWithData(cb); } }); /* cookie like the following: {name:'fruit', value:'apple'} optional fields: path, domain, secure, expiry check the JsonWire doc for details */ protocol.setCookie = methodBuilder({ method: 'POST' , relPath: '/cookie' , data: function(cookie) { // setting secure otherwise selenium server throws if ((typeof cookie !== 'undefined' && cookie !== null) && !((typeof cookie !== 'undefined' && cookie !== null ? cookie.secure : void 0) != null)) { cookie.secure = false; } return { cookie: cookie }; } }); protocol.deleteAllCookies = methodBuilder({ method: 'DELETE' , relPath: '/cookie' }); protocol.deleteCookie = methodBuilder({ method: 'DELETE' , relPath: function(name) { return '/cookie/' + encodeURIComponent(name); } }); // waitForCondition recursive implementation var waitForConditionImpl = function(conditionExpr, limit, poll, cb) { var _this = this; // timeout check if (Date.now() < limit) { // condition check protocol.eval.apply( _this , [conditionExpr, function(err, res) { if(err != null) {return cb(err);} if (res == true) { // condition ok return cb(null, true); } else { // wait for poll and try again setTimeout(function() { waitForConditionImpl.apply(_this, [conditionExpr, limit, poll, cb]); }, poll); } }]); } else { // try one last time protocol.eval.apply( _this, [conditionExpr, function(err, res) { if(err != null) {return cb(err);} if (res == true) { return cb(null, true); } else { // condition nok within timeout return cb("waitForCondition failure for: " + conditionExpr); } }]); } }; // args: (conditionExpr, timeout, poll, cb) // timeout and poll are optional protocol.waitForCondition = function() { // parsing args var args, cb, conditionExpr, limit, poll, timeout, _i; args = 2 <= arguments.length ? __slice.call(arguments, 0, _i = arguments.length - 1) : (_i = 0, []), cb = arguments[_i++]; conditionExpr = args[0], timeout = args[1], poll = args[2]; //defaults timeout = timeout || 1000; poll = poll || 100; // calling implementation limit = Date.now() + timeout; waitForConditionImpl.apply(this, [conditionExpr, limit, poll, cb]); }; // script to be executed in browser var waitForConditionInBrowserJsScript = fs.readFileSync( __dirname + "/browser-scripts/wait-for-cond-in-browser.js", 'utf8'); // args: (conditionExpr, timeout, poll, cb) // timeout and poll are optional protocol.waitForConditionInBrowser = function() { var _this = this; // parsing args var args, cb, conditionExpr, limit, poll, timeout, _i; args = 2 <= arguments.length ? __slice.call(arguments, 0, _i = arguments.length - 1) : (_i = 0, []), cb = arguments[_i++]; conditionExpr = args[0], timeout = args[1], poll = args[2]; //defaults timeout = timeout || 1000; poll = poll || 100; // calling script protocol.executeAsync.apply( _this, [waitForConditionInBrowserJsScript, [conditionExpr,timeout,poll], function(err,res) { if(err != null) {return cb(err);} if(res != true) {return cb("waitForConditionInBrowser failure for: " + conditionExpr);} cb(null, res); } ]); }; module.exports = protocol;