Files
appium/app/firefoxos.js

336 lines
8.7 KiB
JavaScript

"use strict";
var errors = require('./errors')
, _ = require('underscore')
, logger = require('../logger').get('appium')
, net = require('net')
, deviceCommon = require('./device')
, status = require("./uiauto/lib/status")
, getAtomSrc = require('./hybrid/firefoxos/firefoxos-atoms').get
, async = require('async')
, NotYetImplementedError = errors.NotYetImplementedError;
var Firefox = function(opts) {
this.rest = opts.rest;
this.app = opts.app;
this.opts = opts;
this.udid = opts.udid;
this.verbose = opts.verbose;
this.port = opts.port || 2828;
this.queue = [];
this.progress = 0;
this.onStop = function() {};
this.implicitWaitMs = 0;
this.commandTimeoutMs = 60 * 1000;
this.origCommandTimeoutMs = this.commandTimeoutMs;
this.commandTimeout = null;
this.asyncWaitMs = 0;
this.onConnect = null;
this.hasConnected = false;
this.fromActor = null;
this.socket = null;
this.receiveStream = null;
this.expectedRcvBytes = null;
this.capabilities = {
platform: 'LINUX'
, browserName: 'FirefoxOS'
, version: '18.0'
, webStorageEnabled: false
, takesScreenshots: true
, javascriptEnabled: true
, databaseEnabled: false
};
this.lastCmd = null;
this.initCommandMap();
};
Firefox.prototype.start = function(cb, onDie) {
var me = this;
this.socket = new net.Socket();
this.socket.on('close', function() {
onDie(0);
});
this.socket.on('data', _.bind(this.receive, this));
this.socket.connect(this.port, 'localhost', function () { });
this.onConnect = function() {
logger.info("Firefox OS socket connected");
var mainCb = cb;
async.waterfall([
function(cb) { me.getMarionetteId(cb); },
function(cb) { me.createSession(cb); },
function(cb) { me.launchAppByName(cb); },
function(frameId, cb) { me.frame(frameId, cb); }
], function() { mainCb(null, me.sessionId); });
};
};
Firefox.prototype.stop = function(cb) {
logger.info("Stopping firefoxOs connection");
this.proxy({type: 'deleteSession'}, _.bind(function(err) {
if (err) return cb(err);
this.socket.destroy();
cb(0);
}, this));
};
Firefox.prototype.getMarionetteId = function(cb) {
logger.info("Getting marionette id");
this.proxy({type: 'getMarionetteID'}, _.bind(function(err, res) {
if (err) return cb(err);
this.fromActor = res.id;
cb(null);
}, this));
};
Firefox.prototype.createSession = function(cb) {
logger.info("Creating firefox os session");
this.proxy({type: 'newSession'}, _.bind(function(err, res) {
if (err) return cb(err);
this.sessionId = res.value;
cb(null);
}, this));
};
Firefox.prototype.launchAppByName = function(cb) {
logger.info("Launching our app by its name");
var atomSrc = getAtomSrc('gaia_apps');
var wrappedScript = atomSrc +
";GaiaApps.launchWithName('" + this.app +"');";
var cmd = {
type: 'executeAsyncScript'
, args: []
, newSandbox: true
, specialPowers: false
, value: wrappedScript
};
this.proxy(cmd, function(err, res) {
if (err) return cb(err);
cb(null, res.value.frame.ELEMENT);
});
};
Firefox.prototype.receive = function(data) {
var parts, bytes, jsonData;
if (this.receiveStream) {
this.receiveStream += data.toString();
logger.info(data.length + " b more data received, adding to stream (" + this.receiveStream.length + " b)");
try {
data = JSON.parse(this.receiveStream);
this.receiveStream = null;
} catch (e) {
logger.info("Stream still not complete, waiting");
return;
}
} else {
parts = data.toString().split(":");
bytes = parseInt(parts[0], 10);
logger.info("Data received, looking for " + bytes + " bytes");
jsonData = parts.slice(1).join(":");
try {
data = JSON.parse(jsonData);
} catch (e) {
logger.info("Data did not parse, waiting for more");
this.receiveStream = jsonData;
return;
}
}
logger.debug(JSON.stringify(data));
if (!this.hasConnected) {
this.hasConnected = true;
this.fromActor = data.from;
this.onConnect();
} else if (this.cbForCurrentCmd) {
var cb = this.cbForCurrentCmd;
this.progress--;
this.cbForCurrentCmd = null;
if (typeof cb === 'function') {
this.respond(data, cb);
}
this.executeNextCommand();
}
};
Firefox.prototype.proxy = deviceCommon.proxy;
Firefox.prototype.push = function(elem) {
this.queue.push(elem);
this.executeNextCommand();
};
Firefox.prototype.respond = function(data, cb) {
if (typeof data.error !== "undefined") {
cb(new Error(data.error.message));
} else if (typeof data.id !== "undefined") {
cb(null, data);
} else {
cb(null, {
status: status.codes.Success.code
, value: data.value
});
}
};
Firefox.prototype.executeNextCommand = function() {
if (this.queue.length <= 0 || this.progress > 0) {
return;
}
var target = this.queue.shift()
, command = target[0]
, cb = target[1];
this.cbForCurrentCmd = cb;
this.progress++;
command.to = this.fromActor;
var cmdStr = JSON.stringify(command);
cmdStr = cmdStr.length + ':' + cmdStr;
logger.debug("Sending command to firefoxOs: " + cmdStr);
this.socket.write(cmdStr);
};
Firefox.prototype.cmdMap = function() {
var elFn = function(elId) { return {element: elId }; };
return {
implicitWait: ['setSearchTimeout']
, getUrl: ['getUrl']
, findElement: ['findElement', function(strategy, selector) {
return {
using: strategy, value: selector
};
}, function(err, res, cb) {
if (err) return cb(err);
res.value = {ELEMENT: res.value};
cb(null, res);
}]
, click: ['clickElement', elFn]
, setValue: ['sendKeysToElement', function(elId, val) {
return {
element: elId
, value: val.split("")
};
}]
, getText: ['getElementText', elFn]
, getPageSource: ['getPageSource']
, execute: ['executeScript', function(script, params) {
return {
value: script
, args: params
};}]
, frame: ['switchToFrame', elFn]
};
};
Firefox.prototype.notImplementedCmds = function() {
return [
'equalsWebElement'
, 'findElements'
, 'findElementFromElement'
, 'findElementsFromElement'
, 'fireEvent'
, 'complexTap'
, 'flick'
, 'getPageSourceXML'
, 'swipe'
, 'hideKeyboard'
, 'clear'
, 'getName'
, 'getAttribute'
, 'getCssProperty'
, 'getLocation'
, 'getSize'
, 'getWindowSize'
, 'getPageIndex'
, 'keyevent'
, 'back'
, 'forward'
, 'refresh'
, 'keys'
, 'leaveWebView'
, 'elementDisplayed'
, 'elementEnabled'
, 'elementSelected'
, 'getAlertText'
, 'setAlertText'
, 'postAcceptAlert'
, 'postDismissAlert'
, 'asyncScriptTimeout'
, 'setOrientation'
, 'getOrientation'
, 'moveTo'
, 'clickCurrent'
, 'fakeFlickElement'
, 'executeAsync'
, 'title'
, 'submit'
, 'url'
, 'active'
, 'getWindowHandle'
, 'setWindow'
, 'closeWindow'
, 'getWindowHandles'
, 'getCommandTimeout'
, 'receiveAsyncResponse'
, 'setValueImmediate'
, 'findAndAct'
, 'getCookies'
, 'setCookie'
, 'deleteCookie'
, 'deleteCookies'
, 'getCurrentActivity'
];
};
Firefox.prototype.initCommandMap = function() {
var nyiCmds = this.notImplementedCmds();
// create controller functions dynamically for implemented commands
_.each(this.cmdMap(), _.bind(function(cmdInfo, controller) {
if (_.contains(nyiCmds, controller)) {
throw new Error("Controller " + controller + " is listed in both " +
"implemented and not-yet-implemented lists. Fix this " +
"before moving on!");
}
this[controller] = _.bind(function() {
var args = Array.prototype.slice.call(arguments, 0);
var cb;
var outerCb = args[args.length - 1];
if (typeof cmdInfo[2] === 'function') {
cb = function(err, res) {
cmdInfo[2](err, res, outerCb);
};
} else {
cb = outerCb;
}
args = args.slice(0, args.length - 1);
var cmd = {
type: cmdInfo[0]
};
if (typeof cmdInfo[1] === 'function') {
cmd = _.extend(cmd, cmdInfo[1].apply(this, args));
} else if (typeof cmdInfo[1] === "undefined" && args.length > 0) {
cmd = _.extend(cmd, {value: args[0]});
}
this.proxy(cmd, cb);
}, this);
}, this));
// throw not yet implemented for any command in nyi list
_.each(nyiCmds, _.bind(function(controller) {
this[controller] = _.bind(function() {
var args = Array.prototype.slice.call(arguments, 0);
var cb = args[args.length - 1];
cb(new NotYetImplementedError(), null);
}, this);
}, this));
};
module.exports = function(opts) {
return new Firefox(opts);
};