var http = require('http') , __slice = Array.prototype.slice; var strip = function strip(str) { var x = []; for (var i = 0; i < str.length; i++) { if (str.charCodeAt(i)) { x.push(str.charAt(i)); } } return x.join(''); }; var newError = function(opts) { var err = new Error(); for (var k in opts) { err[k] = opts[k] } // nicer error output err.inspect = function() { var res = ""; for (var k in this) { var _this = this; (function() { var v = _this[k]; if (typeof v === 'object') { if ((v["class"] != null) && v["class"].match(/org.openqa.selenium.remote.Response/)) { // for selenium classes, hidding long fields or field with // duplicate information var vAsStr = JSON.stringify(v, function(key, value) { if (key === 'screen' || key === 'stackTrace' || key === 'buildInformation' || key === 'localizedMessage') { return '[hidden]'; } else { return value; } }, " "); res = res + k + ": " + vAsStr + "\n"; } else { // for other objects making sure output is not too long var vAsStr = JSON.stringify(v, undefined, " "); var maxLength = 1000; if (vAsStr.length > maxLength) { vAsStr = vAsStr.substr(0, maxLength) + "\n..."; } res = res + k + ": " + vAsStr + "\n"; } } else if (typeof v != 'function') { // printing non object types without modif res = res + k + ": " + v + "\n"; } })(); }; return res; }; return err; }; var isWebDriverException = function(res) { var _ref; if ((typeof res !== "undefined" && res !== null ? (_ref = res["class"]) != null ? _ref.indexOf('WebDriverException') : void 0 : void 0) > 0) { return true; } return false; } // just calls the callback when there is no result var simpleCallback = function(cb) { return function(res) { if(res==null) { // expected behaviour for quit if(cb!=null){ return cb();} }else{ res.setEncoding('utf8'); var data = ''; res.on('data', function(chunk) { data += chunk.toString(); }); res.on('end', function() { if(data == '') { // expected behaviour return cb() } else { // something wrong if(cb!=null){ return cb(new Error( {message:'Unexpected data in simpleCallback.', data:data}) ); } } }); } }; }; // base for all callback handling data var callbackWithDataBase = function(cb) { return function(res) { res.setEncoding('utf8'); var data = ''; res.on('data', function(chunk) { data += chunk.toString(); }); res.on('end', function() { var obj; try { obj = JSON.parse(strip(data)); } catch (e) { return cb(newError({message:'Not JSON response', data:data})); } if (obj.status > 0) { cb(newError( {message:'Error response status.',status:obj.status,cause:obj})); } else { cb(null, obj); } }); } }; // retrieves field value from result var callbackWithData = function(cb) { return callbackWithDataBase(function(err,obj) { if(err != null) {return cb(err);} if(isWebDriverException(obj.value)) {return cb(newError( {message:obj.value.message,cause:obj.value}));} cb(null, obj.value); }); }; // retrieves ONE element var elementCallback = function(cb) { return callbackWithDataBase(function(err, obj) { if(err != null) {return cb(err);} if(isWebDriverException(obj.value)) {return cb(newError( {message:obj.value.message,cause:obj.value}));} if (!obj.value.ELEMENT) { cb(newError( {message:"no ELEMENT in response value field.",cause:obj})); } else { cb(null, obj.value.ELEMENT); } }); }; // retrieves SEVERAL elements var elementsCallback = function(cb) { return callbackWithDataBase(function(err, obj) { if(err != null) {return cb(err);} if(isWebDriverException(obj.value)) {return cb(newError( {message:obj.value.message,cause:obj.value}));} if (!(obj.value instanceof Array)) {return cb(newError( {message:"Response value field is not an Array.", cause:obj.value}));} var i, elements = []; for (i = 0; i < obj.value.length; i++) { elements.push(obj.value[i].ELEMENT); } cb(null, elements); }); }; var newHttpOpts = function(method) { var opts = new Object(); opts.method = method; for (var o in this.options) { opts[o] = this.options[o]; } opts.headers = {}; opts.headers['Connection'] = 'keep-alive'; if (opts.method === 'POST' || opts.method === 'GET') opts.headers['Accept'] = 'application/json'; if (opts.method == 'POST') opts.headers['Content-Type'] = 'application/json; charset=UTF-8'; return opts; }; // session initialization var init = function(desired, cb) { var _this = this; //allow desired ovveride to be left out if (typeof desired == 'function') { cb = desired; desired = {}; } // making copy var _desired = {}; for (var k in desired) { _desired[k] = desired[k]; } // defaulting capabilities when necessary for (var k in this.defaultCapabilities) { _desired[k] = _desired[k] || this.defaultCapabilities[k]; } // http options var httpOpts = newHttpOpts.apply(this, ['POST']); // authentication (for saucelabs) if ((_this.username != null) && (_this.accessKey != null)) { var authString = _this.username + ':' + _this.accessKey; var buf = new Buffer(authString); httpOpts['headers'] = { 'Authorization': 'Basic ' + buf.toString('base64') }; } // building request var req = http.request(httpOpts, function(res) { var data = ''; res.on('data', function(chunk) { data += chunk; }); res.on('end', function() { if (res.headers.location == undefined) { console.log('\x1b[31mError\x1b[0m: The environment you requested was unavailable.\n'); console.log('\x1b[33mReason\x1b[0m:\n'); console.log(data); console.log('\nFor the available values please consult the WebDriver JSONWireProtocol,'); console.log('located at: \x1b[33mhttp://code.google.com/p/selenium/wiki/JsonWireProtocol#/session\x1b[0m'); if (cb) cb({ message: 'The environment you requested was unavailable.' }); return; } var locationArr = res.headers.location.split('/'); _this.sessionID = locationArr[locationArr.length - 1]; _this.emit('status', '\nDriving the web on session: ' + _this.sessionID + '\n'); if (cb) { cb(null, _this.sessionID) } }); }); req.on('error', function(e) { cb(e); }); // writting data req.write(JSON.stringify({desiredCapabilities: _desired})); // sending req.end(); }; // used to build all the methods except init var methodBuilder = function(builderOpt) { // by default we call simpleCallBack(cb) assuming cb is the last argument var defaultCb = function() { var args, cb, _i; args = 2 <= arguments.length ? __slice.call(arguments, 0, _i = arguments.length - 1) : (_i = 0, []), cb = arguments[_i++]; return simpleCallback(cb); }; return function(cb) { var _this = this; // parsing arguments var args = 1 <= arguments.length ? __slice.call(arguments, 0) : []; // http options init var httpOpts = newHttpOpts.apply(this, [builderOpt.method]); // retrieving path information var relPath = builderOpt.relPath; if (typeof relPath === 'function') { relPath = relPath.apply(this, args) } var absPath = builderOpt.absPath; if (typeof absPath === 'function') { absPath = absPath.apply(this, args) } // setting path in http options if (this.sessionID != null) { httpOpts['path'] += '/' + this.sessionID; } if (relPath) { httpOpts['path'] += relPath; } if (absPath) { httpOpts['path'] = absPath;} // building callback cb = (builderOpt.cb || defaultCb).apply(this, args); // wrapping cb if we need to emit a message if (builderOpt.emit != null) { var _cb = cb; cb = function(res) { if (builderOpt.emit != null) { _this.emit(builderOpt.emit.event, builderOpt.emit.message); } if (_cb) { _cb(); } }; } // logging _this.emit('command', httpOpts['method'], httpOpts['path'].replace(this.sessionID, ':sessionID') .replace(this.basePath, '') ); // building request var req = http.request(httpOpts, cb); req.on('error', function(e) { cb(e); }); // writting data var data = ''; if (builderOpt.data != null) { data = builderOpt.data.apply(this, args); } if (typeof data === 'object') { data = JSON.stringify(data); } req.write(data); //sending req.end(); }; }; exports.simpleCallback = simpleCallback; exports.callbackWithData = callbackWithData; exports.elementCallback = elementCallback; exports.elementsCallback = elementsCallback; exports.init = init; exports.methodBuilder = methodBuilder;