Plato on Github
Report Home
lib/marionette.js
Maintainability
70.54
Lines of code
308
Difficulty
37.04
Estimated Errors
1.85
Function weight
By Complexity
By SLOC
/*! * * Copyright (c) 2013 Sebastian Golasch * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. * */ 'use strict'; // ext. libs var fs = require('fs'); var net = require('net'); var Q = require('q'); /** * Firefox Autotest (Marionette) * connection handler & request executor * * @module FirefoxDriver * @class Marionette * @namespace FirefoxDriver */ /** * Configures the marionette socket * Prepares the object properties * Loads the marionette commands * * @constructor * @param {EventEmitter} events */ var Marionette = function (events) { // configure the marionette socket this.socket = new net.Socket(); this.socket.on('error', this._onError.bind(this)); this.socket.on('data', this._onData.bind(this)); // configure marionette session container this._marionetteID = null; // store events instance this.events = events; // register marionette id setter this.events.on('marionette:setMarionetteID', function (id) { this._marionetteID = id; }.bind(this)); // prepare the commands property this.commands = {}; // prepare the last send command property this._lastCommand = null; // prepare the last message length property this._lastMessageLength = null; // load the commands this._loadCommands(); }; /** * Opens the marionette socket * * @method connect * @param {integer} marionettePort * @return {Q.promise} promise * @public */ Marionette.prototype.connect = function (marionettePort) { var deferred = Q.defer(); this.socket.on('connect', deferred.resolve); this.socket.connect(marionettePort); return deferred.promise; }; /** * Closes the marionette socket & cleans up * * @method kill * @return {Q.promise} promise * @public */ Marionette.prototype.kill = function () { var deferred = Q.defer(); this.socket.on('end', deferred.makeNodeResolver()); this.socket.end(); return deferred.promise; }; /** * Generates a function & an event listener for every marionette command * * @method addCommand * @param {object} command * @return {object} command * @public */ Marionette.prototype.addCommand = function (command) { // generate the command function this.commands[command.name] = function (params) { var req = {}; var deferred = Q.defer(); deferred.promise.then(function (data) { this.events.emit('marionette:cmd:' + command.name + ':response', JSON.stringify(data)); }.bind(this)); // parse the command skeleton & create a message with values Object.keys(command.request).forEach(function (key){ // cookie related hack if (command.name === 'cookies' && this._parseRequestKey(command.request[key], params) === '') { } else { req[key] = this._parseRequestKey(command.request[key], params); } }.bind(this)); // issue the command this._sendCommand(req, deferred); }.bind(this); // generate event listener for every marionette command this.events.on('marionette:cmd:' + command.name, this.commands[command.name].bind(this)); return command; }; /** * Loads the marionette commands from the commands library * * @method _loadCommands * @chainable * @private */ Marionette.prototype._loadCommands = function () { var dir = __dirname + '/commands/marionette/'; fs.readdirSync(dir).forEach(function (file) { require(dir + file)(this); }.bind(this)); return this; }; /** * Templating function that replaces placeholders in commands * with their actual values * * @method _parseRequestKey * @param {string} placeholder * @param {object} params * @return {string} content * @private */ Marionette.prototype._parseRequestKey = function (placeholder, params) { if (typeof placeholder !== 'object' && placeholder[0] !== ':') { return placeholder; } // check the params if (!params) { params = {}; } // add the marionetteID to the params if (typeof placeholder !== 'object') { params.marionetteId = this._marionetteID; } var content = ''; if (typeof placeholder !== 'object') { Object.keys(params).forEach(function (key){ if (key === placeholder.substr(1)) { content = params[key]; } }.bind(this)); } // check if we have a params object (and replace also there) if (typeof placeholder === 'object') { content = {}; Object.keys(params).forEach(function (key) { Object.keys(placeholder).forEach(function (name) { if (placeholder[name] && placeholder[name].substring(1) === key) { content[name] = params[key]; } }.bind(this)); }.bind(this)); Object.keys(placeholder).forEach(function (name) { if (placeholder[name].substr(0, 1) !== ':') { content[name] = placeholder[name]; } }.bind(this)); } return content; }; /** * Will be called if a socket error occurs * * @method _onError * @param {buffer} err * @chainable * @private */ Marionette.prototype._onError = function (err) { // TODO: Use the super not yet implemented dalek error handler console.log('ERR', String(err)); process.exit(); return this; }; /** * Handles the incoming data from the marionette sockets * * @method _onData * @param {buffer} data * @chainable * @private */ Marionette.prototype._onData = function (data) { var def = Q.defer(); var raw = String(data); // extract the header out of the raw message var colon = raw.indexOf(':'); var length = raw.substr(0, colon); var msg = raw.substring(colon + 1); // check if this is incoming data is a sibling // to a previous received message if (isNaN(parseInt(length, 10))) { length = this._lastResponseLength; } // store message data this._lastResponseLength = length; this._lastResponse += msg; this._lastRawResponse += raw; // check if the message is complete if((this._lastResponse.length + 1) >= parseInt(length, 10)) { // resolve the message def.resolve(this._lastRawResponse); // reset our message storage this._lastRawResponse = ''; this._lastResponse = ''; this._lastResponseLength = null; } // release the message when it is completely received Q.when(def.promise).then(function(data) { // parse the message & call the resolving function var parsedMessage = this._parseMessage(data); // avoid resolving the initial handshake command if (!parsedMessage.applicationType) { this._lastCommand.resolve(parsedMessage); } }.bind(this)); return this; }; /** * Parses a marionette response message * * @method _parseMessage * @param {buffer} data * @return {object} message * @private */ Marionette.prototype._parseMessage = function (data) { var raw = String(data); var colon = raw.indexOf(':'); var message = JSON.parse(raw.substring(colon + 1)); return message; }; /** * Issues a command to the marionette server * Generates a reference to the last called command, * so that we are able to call the right receiver function for * the response from marionette * * @method _sendCommand * @param {object} command * @param {Q.promise} deferred * @chainable * @private */ Marionette.prototype._sendCommand = function (command, deferred) { // serialize message var commandSerialized = JSON.stringify(command); var msg = commandSerialized.length + ':' + commandSerialized; // reference promise this._lastCommand = deferred; // send command this.socket.write(msg); return this; }; module.exports = Marionette;