Bug 712643 - land Marionette on m-c. r=mossop,robcee

This commit is contained in:
Joel Maher 2012-03-22 11:19:57 -04:00
parent a470bd0c67
commit 7040eedb07
25 changed files with 3053 additions and 1 deletions

View File

@ -122,6 +122,14 @@ if [ "$COMPILER_DEPEND" = "" -a "$MOZ_NATIVE_MAKEDEPEND" = "" ]; then
" "
fi fi
if [ "$ENABLE_MARIONETTE" ]; then
add_makefiles "
testing/marionette/Makefile
testing/marionette/components/Makefile
testing/marionette/tests/Makefile
"
fi
if [ "$ENABLE_TESTS" ]; then if [ "$ENABLE_TESTS" ]; then
add_makefiles " add_makefiles "
build/autoconf/test/Makefile build/autoconf/test/Makefile

View File

@ -455,6 +455,10 @@ pref("ril.data.apn", "");
pref("ril.data.user", ""); pref("ril.data.user", "");
pref("ril.data.passwd", ""); pref("ril.data.passwd", "");
//Enable/disable marionette server, set listening port
pref("marionette.defaultPrefs.enabled", true);
pref("marionette.defaultPrefs.port", 2828);
#ifdef MOZ_UPDATER #ifdef MOZ_UPDATER
pref("app.update.enabled", true); pref("app.update.enabled", true);
pref("app.update.auto", true); pref("app.update.auto", true);

View File

@ -46,7 +46,7 @@ MOZ_OFFICIAL_BRANDING_DIRECTORY=b2g/branding/official
# MOZ_APP_DISPLAYNAME is set by branding/configure.sh # MOZ_APP_DISPLAYNAME is set by branding/configure.sh
MOZ_SAFE_BROWSING= MOZ_SAFE_BROWSING=
MOZ_SERVICES_SYNC= MOZ_SERVICES_SYNC=1
MOZ_WEBSMS_BACKEND=1 MOZ_WEBSMS_BACKEND=1
MOZ_DISABLE_DOMCRYPTO=1 MOZ_DISABLE_DOMCRYPTO=1

View File

@ -617,6 +617,10 @@ bin/components/@DLL_PREFIX@nkgnomevfs@DLL_SUFFIX@
@BINPATH@/components/B2GComponents.manifest @BINPATH@/components/B2GComponents.manifest
@BINPATH@/components/B2GComponents.xpt @BINPATH@/components/B2GComponents.xpt
@BINPATH@/components/CameraContent.js @BINPATH@/components/CameraContent.js
@BINPATH@/chrome/marionette@JAREXT@
@BINPATH@/chrome/marionette.manifest
@BINPATH@/components/MarionetteComponents.manifest
@BINPATH@/components/marionettecomponent.js
@BINPATH@/components/AlertsService.js @BINPATH@/components/AlertsService.js
@BINPATH@/components/ContentPermissionPrompt.js @BINPATH@/components/ContentPermissionPrompt.js
#ifdef MOZ_UPDATER #ifdef MOZ_UPDATER

View File

@ -135,6 +135,7 @@ MOZ_LIBSTDCXX_HOST_VERSION=@MOZ_LIBSTDCXX_HOST_VERSION@
INCREMENTAL_LINKER = @INCREMENTAL_LINKER@ INCREMENTAL_LINKER = @INCREMENTAL_LINKER@
MACOSX_DEPLOYMENT_TARGET = @MACOSX_DEPLOYMENT_TARGET@ MACOSX_DEPLOYMENT_TARGET = @MACOSX_DEPLOYMENT_TARGET@
ENABLE_TESTS = @ENABLE_TESTS@ ENABLE_TESTS = @ENABLE_TESTS@
ENABLE_MARIONETTE = @ENABLE_MARIONETTE@
IBMBIDI = @IBMBIDI@ IBMBIDI = @IBMBIDI@
MOZ_UNIVERSALCHARDET = @MOZ_UNIVERSALCHARDET@ MOZ_UNIVERSALCHARDET = @MOZ_UNIVERSALCHARDET@
ACCESSIBILITY = @ACCESSIBILITY@ ACCESSIBILITY = @ACCESSIBILITY@

View File

@ -6437,6 +6437,14 @@ MOZ_ARG_DISABLE_BOOL(tests,
ENABLE_TESTS=, ENABLE_TESTS=,
ENABLE_TESTS=1 ) ENABLE_TESTS=1 )
dnl ========================================================
dnl Marionette
dnl ========================================================
MOZ_ARG_ENABLE_BOOL(marionette,
[ --enable-marionette Enable Marionette for remote testing and control],
ENABLE_MARIONETTE=1,
ENABLE_MARIONETTE)
dnl ======================================================== dnl ========================================================
dnl parental controls (for Windows Vista) dnl parental controls (for Windows Vista)
dnl ======================================================== dnl ========================================================
@ -8381,6 +8389,7 @@ AC_SUBST(JAR)
AC_SUBST(MOZ_PROFILELOCKING) AC_SUBST(MOZ_PROFILELOCKING)
AC_SUBST(ENABLE_TESTS) AC_SUBST(ENABLE_TESTS)
AC_SUBST(ENABLE_MARIONETTE)
AC_SUBST(IBMBIDI) AC_SUBST(IBMBIDI)
AC_SUBST(MOZ_UNIVERSALCHARDET) AC_SUBST(MOZ_UNIVERSALCHARDET)
AC_SUBST(ACCESSIBILITY) AC_SUBST(ACCESSIBILITY)

View File

@ -0,0 +1,19 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this file,
# You can obtain one at http://mozilla.org/MPL/2.0/.
DEPTH = ../..
topsrcdir = @top_srcdir@
srcdir = @srcdir@
VPATH = @srcdir@
include $(DEPTH)/config/autoconf.mk
ifdef ENABLE_MARIONETTE
DIRS += components
ifdef ENABLE_TESTS
DIRS += tests
endif
endif
include $(topsrcdir)/config/rules.mk

View File

@ -0,0 +1,17 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this file,
# You can obtain one at http://mozilla.org/MPL/2.0/.
DEPTH = ../../..
topsrcdir = @top_srcdir@
srcdir = @srcdir@
VPATH = @srcdir@
include $(DEPTH)/config/autoconf.mk
EXTRA_PP_COMPONENTS = \
MarionetteComponents.manifest \
marionettecomponent.js \
$(NULL)
include $(topsrcdir)/config/rules.mk

View File

@ -0,0 +1,4 @@
# Marionette
component {786a1369-dca5-4adc-8486-33d23c88010a} marionettecomponent.js
contract @mozilla.org/marionette;1 {786a1369-dca5-4adc-8486-33d23c88010a}
category profile-after-change MarionetteComponent @mozilla.org/marionette;1

View File

@ -0,0 +1,89 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
const MARIONETTE_CONTRACTID = "@mozilla.org/marionette;1";
const MARIONETTE_CID = Components.ID("{786a1369-dca5-4adc-8486-33d23c88010a}");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
function MarionetteComponent() {
this._loaded = false;
}
MarionetteComponent.prototype = {
classDescription: "Marionette component",
classID: MARIONETTE_CID,
contractID: MARIONETTE_CONTRACTID,
QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver]),
_xpcom_categories: [{category: "profile-after-change", service: true}],
observe: function mc_observe(aSubject, aTopic, aData) {
let observerService = Services.obs;
switch (aTopic) {
case "profile-after-change":
if (Services.prefs.getBoolPref('marionette.defaultPrefs.enabled')) {
// set up the logger
Cu.import("resource://gre/modules/FileUtils.jsm");
Cu.import("resource://gre/modules/services-sync/log4moz.js");
let logger = Log4Moz.repository.getLogger("Marionette");
logger.level = Log4Moz.Level["All"];
let logf = FileUtils.getFile('ProfD', ['marionette.log']);
let formatter = new Log4Moz.BasicFormatter();
logger.addAppender(new Log4Moz.FileAppender(logf, formatter));
logger.info("MarionetteComponent loaded");
//add observers
observerService.addObserver(this, "final-ui-startup", false);
observerService.addObserver(this, "xpcom-shutdown", false);
}
else {
logger.info("marionette not enabled");
}
break;
case "final-ui-startup":
observerService.removeObserver(this, "final-ui-startup");
this.init();
break;
case "xpcom-shutdown":
observerService.removeObserver(this, "xpcom-shutdown");
this.uninit();
break;
}
},
init: function mc_init() {
if (!this._loaded) {
this._loaded = true;
let port;
try {
port = Services.prefs.getIntPref('marionette.defaultPrefs.port');
}
catch(e) {
port = 2828;
}
try {
Cu.import('resource:///modules/devtools/dbg-server.jsm');
DebuggerServer.addActors('chrome://marionette/content/marionette-actors.js');
DebuggerServer.initTransport();
DebuggerServer.openListener(port, true);
}
catch(e) {
logger.error('exception: ' + e.name + ', ' + e.message);
}
}
},
uninit: function mc_uninit() {
DebuggerServer.closeListener();
this._loaded = false;
},
};
const NSGetFactory = XPCOMUtils.generateNSGetFactory([MarionetteComponent]);

View File

@ -0,0 +1,7 @@
marionette.jar:
% content marionette %content/
content/marionette-actors.js (marionette-actors.js)
content/marionette-listener.js (marionette-listener.js)
content/marionette-elements.js (marionette-elements.js)
content/marionette-log-obj.js (marionette-log-obj.js)
content/marionette-simpletest.js (marionette-simpletest.js)

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,403 @@
/* -*- Mode: javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
/**
* The ElementManager manages DOM references and interactions with elements.
* According to the WebDriver spec (http://code.google.com/p/selenium/wiki/JsonWireProtocol), the
* server sends the client an element reference, and maintains the map of reference to element.
* The client uses this reference when querying/interacting with the element, and the
* server uses maps this reference to the actual element when it executes the command.
*/
let EXPORTED_SYMBOLS = ["ElementManager", "CLASS_NAME", "SELECTOR", "ID", "NAME", "LINK_TEXT", "PARTIAL_LINK_TEXT", "TAG", "XPATH"];
let uuidGen = Components.classes["@mozilla.org/uuid-generator;1"]
.getService(Components.interfaces.nsIUUIDGenerator);
let CLASS_NAME = "class name";
let SELECTOR = "css selector";
let ID = "id";
let NAME = "name";
let LINK_TEXT = "link text";
let PARTIAL_LINK_TEXT = "partial link text";
let TAG = "tag name";
let XPATH = "xpath";
function ElementException(msg, num, stack) {
this.message = msg;
this.num = num;
this.stack = stack;
}
/* NOTE: Bug 736592 has been created to replace seenItems with a weakRef map */
function ElementManager(notSupported) {
this.searchTimeout = 0;
this.seenItems = {};
this.timer = Components.classes["@mozilla.org/timer;1"].createInstance(Components.interfaces.nsITimer);
this.elementStrategies = [CLASS_NAME, SELECTOR, ID, NAME, LINK_TEXT, PARTIAL_LINK_TEXT, TAG, XPATH];
for (let i = 0; i < notSupported.length; i++) {
this.elementStrategies.splice(this.elementStrategies.indexOf(notSupported[i]), 1);
}
}
ElementManager.prototype = {
/**
* Reset values
*/
reset: function EM_clear() {
this.searchTimeout = 0;
this.seenItems = {};
},
/**
* Add element to list of seen elements
*
* @param nsIDOMElement element
* The element to add
*
* @return string
* Returns the server-assigned reference ID
*/
addToKnownElements: function EM_addToKnownElements(element) {
for (let i in this.seenItems) {
if (this.seenItems[i] == element) {
return i;
}
}
var id = uuidGen.generateUUID().toString();
this.seenItems[id] = element;
return id;
},
/**
* Retrieve element from its unique ID
*
* @param String id
* The DOM reference ID
* @param nsIDOMWindow win
* The window that contains the element
*
* @returns nsIDOMElement
* Returns the element or throws Exception if not found
*/
getKnownElement: function EM_getKnownElement(id, win) {
let el = this.seenItems[id];
if (!el) {
throw new ElementException("Element has not been seen before", 17, null);
}
el = el;
if (!(el.ownerDocument == win.document)) {
throw new ElementException("Stale element reference", 10, null);
}
return el;
},
/**
* Convert values to primitives that can be transported over the Marionette
* JSON protocol.
*
* @param object val
* object to be wrapped
*
* @return object
* Returns a JSON primitive or Object
*/
wrapValue: function EM_wrapValue(val) {
let result;
switch(typeof(val)) {
case "undefined":
result = null;
break;
case "string":
case "number":
case "boolean":
result = val;
break;
case "object":
if (Object.prototype.toString.call(val) == '[object Array]') {
result = [];
for (let i in val) {
result.push(this.wrapValue(val[i]));
}
}
else if (val == null) {
result = null;
}
// nodeType 1 == 'element'
else if (val.nodeType == 1) {
for(let i in this.seenItems) {
if (this.seenItems[i] == val) {
result = {'ELEMENT': i};
}
}
result = {'ELEMENT': this.addToKnownElements(val)};
}
else {
result = {};
for (let prop in val) {
result[prop] = this.wrapValue(val[prop]);
}
}
break;
}
return result;
},
/**
* Convert any ELEMENT references in 'args' to the actual elements
*
* @param object args
* Arguments passed in by client
* @param nsIDOMWindow win
* The window that contains the elements
*
* @returns object
* Returns the objects passed in by the client, with the
* reference IDs replaced by the actual elements.
*/
convertWrappedArguments: function EM_convertWrappedArguments(args, win) {
let converted;
switch (typeof(args)) {
case 'number':
case 'string':
case 'boolean':
converted = args;
break;
case 'object':
if (args == null) {
converted = null;
}
else if (Object.prototype.toString.call(args) == '[object Array]') {
converted = [];
for (let i in args) {
converted.push(this.convertWrappedArguments(args[i], win));
}
}
else if (typeof(args['ELEMENT'] === 'string') &&
args.hasOwnProperty('ELEMENT')) {
converted = this.getKnownElement(args['ELEMENT'], win);
if (converted == null)
throw new ElementException("Unknown element: " + args['ELEMENT'], 500, null);
}
else {
converted = {};
for (let prop in args) {
converted[prop] = this.convertWrappedArguments(args[prop], win);
}
}
break;
}
return converted;
},
/*
* Execute* helpers
*/
/**
* Return an object with any namedArgs applied to it. Used
* to let clients use given names when refering to arguments
* in execute calls, instead of using the arguments list.
*
* @param object args
* list of arguments being passed in
*
* @return object
* If '__marionetteArgs' is in args, then
* it will return an object with these arguments
* as its members.
*/
applyNamedArgs: function EM_applyNamedArgs(args) {
namedArgs = {};
args.forEach(function(arg) {
if (typeof(arg['__marionetteArgs']) === 'object') {
for (let prop in arg['__marionetteArgs']) {
namedArgs[prop] = arg['__marionetteArgs'][prop];
}
}
});
return namedArgs;
},
/**
* Find an element or elements starting at the document root
* using the given search strategy. Search
* will continue until the search timelimit has been reached.
*
* @param object values
* The 'using' member of values will tell us which search
* method to use. The 'value' member tells us the value we
* are looking for.
* If this object has a 'time' member, this number will be
* used to see if we have hit the search timelimit.
* @param nsIDOMElement rootNode
* The document root
* @param function notify
* The notification callback used when we are returning
* @param boolean all
* If true, all found elements will be returned.
* If false, only the first element will be returned.
*
* @return nsIDOMElement or list of nsIDOMElements
* Returns the element(s) by calling the notify function.
*/
find: function EM_find(values, rootNode, notify, all) {
let startTime = values.time ? values.time : new Date().getTime();
if (this.elementStrategies.indexOf(values.using) < 0) {
throw new ElementException("No such strategy.", 17, null);
}
let found = all ? this.findElements(values.using, values.value, rootNode) : this.findElement(values.using, values.value, rootNode);
if (found) {
let type = Object.prototype.toString.call(found);
if ((type == '[object Array]') || (type == '[object HTMLCollection]')) {
let ids = []
for (let i = 0 ; i < found.length ; i++) {
ids.push(this.addToKnownElements(found[i]));
}
notify(ids);
}
else {
let id = this.addToKnownElements(found);
notify(id);
}
return;
} else {
if (this.searchTimeout == 0 || new Date().getTime() - startTime > this.searchTimeout) {
throw new ElementException("Unable to locate element: " + values.value, 7, null);
} else {
values.time = startTime;
this.timer.initWithCallback(this.find.bind(this, values, rootNode, notify, all), 100, Components.interfaces.nsITimer.TYPE_ONE_SHOT);
}
}
},
/**
* Helper method to find. Finds one element using find's criteria
*
* @param string using
* String identifying which search method to use
* @param string value
* Value to look for
* @param nsIDOMElement rootNode
* Document root
*
* @return nsIDOMElement
* Returns found element or throws Exception if not found
*/
findElement: function EM_findElement(using, value, rootNode) {
let element;
switch (using) {
case ID:
element = rootNode.getElementById(value);
break;
case NAME:
element = rootNode.getElementsByName(value)[0];
break;
case CLASS_NAME:
element = rootNode.getElementsByClassName(value)[0];
break;
case TAG:
element = rootNode.getElementsByTagName(value)[0];
break;
case XPATH:
element = rootNode.evaluate(value, rootNode, null,
Components.interfaces.nsIDOMXPathResult.FIRST_ORDERED_NODE_TYPE, null).
singleNodeValue;
break;
case LINK_TEXT:
case PARTIAL_LINK_TEXT:
let allLinks = rootNode.getElementsByTagName('A');
for (let i = 0; i < allLinks.length && !element; i++) {
let text = allLinks[i].text;
if (PARTIAL_LINK_TEXT == using) {
if (text.indexOf(value) != -1) {
element = allLinks[i];
}
} else if (text == value) {
element = allLinks[i];
}
}
break;
case SELECTOR:
element = rootNode.querySelector(value);
break;
default:
throw new ElementException("No such strategy", 500, null);
}
return element;
},
/**
* Helper method to find. Finds all element using find's criteria
*
* @param string using
* String identifying which search method to use
* @param string value
* Value to look for
* @param nsIDOMElement rootNode
* Document root
*
* @return nsIDOMElement
* Returns found elements or throws Exception if not found
*/
findElements: function EM_findElements(using, value, rootNode) {
let elements = [];
switch (using) {
case ID:
value = './/*[@id="' + value + '"]';
case XPATH:
values = rootNode.evaluate(value, rootNode, null,
Components.interfaces.nsIDOMXPathResult.ORDERED_NODE_ITERATOR_TYPE, null)
let element = values.iterateNext();
while (element) {
elements.push(element);
element = values.iterateNext();
}
break;
case NAME:
elements = rootNode.getElementsByName(value);
break;
case CLASS_NAME:
elements = rootNode.getElementsByClassName(value);
break;
case TAG:
elements = rootNode.getElementsByTagName(value);
break;
case LINK_TEXT:
case PARTIAL_LINK_TEXT:
let allLinks = rootNode.getElementsByTagName('A');
for (let i = 0; i < allLinks.length; i++) {
let text = allLinks[i].text;
if (PARTIAL_LINK_TEXT == using) {
if (text.indexOf(value) != -1) {
elements.push(allLinks[i]);
}
} else if (text == value) {
elements.push(allLinks[i]);
}
}
break;
case SELECTOR:
elements = rootNode.querySelectorAll(value);
break;
default:
throw new ElementException("No such strategy", 500, null);
}
return elements;
},
/**
* Sets the timeout for searching for elements with find element
*
* @param number value
* Timeout value in milliseconds
*/
setSearchTimeout: function EM_setSearchTimeout(value) {
this.searchTimeout = parseInt(value);
if(isNaN(this.searchTimeout)){
throw new ElementException("Not a Number", 500, null);
}
},
}

View File

@ -0,0 +1,534 @@
/* -*- Mode: javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
let Cu = Components.utils;
let uuidGen = Components.classes["@mozilla.org/uuid-generator;1"]
.getService(Components.interfaces.nsIUUIDGenerator);
let loader = Components.classes["@mozilla.org/moz/jssubscript-loader;1"]
.getService(Components.interfaces.mozIJSSubScriptLoader);
loader.loadSubScript("chrome://marionette/content/marionette-simpletest.js");
loader.loadSubScript("chrome://marionette/content/marionette-log-obj.js");
Components.utils.import("chrome://marionette/content/marionette-elements.js");
let marionetteLogObj = new MarionetteLogObj();
let isB2G = false;
let marionetteTimeout = null;
let winUtil = content.QueryInterface(Components.interfaces.nsIInterfaceRequestor).getInterface(Components.interfaces.nsIDOMWindowUtils);
let listenerId = null; //unique ID of this listener
let activeFrame = null;
let win = content;
let elementManager = new ElementManager([]);
/**
* Called when listener is first started up.
* The listener sends its unique window ID and its current URI to the actor.
* If the actor returns an ID, we start the listeners. Otherwise, nothing happens.
*/
function registerSelf() {
let register = sendSyncMessage("Marionette:register", {value: winUtil.outerWindowID, href: content.location.href});
if (register[0]) {
listenerId = register[0];
startListeners();
}
}
/**
* Start all message listeners
*/
function startListeners() {
addMessageListener("Marionette:newSession" + listenerId, newSession);
addMessageListener("Marionette:executeScript" + listenerId, executeScript);
addMessageListener("Marionette:setScriptTimeout" + listenerId, setScriptTimeout);
addMessageListener("Marionette:executeAsyncScript" + listenerId, executeAsyncScript);
addMessageListener("Marionette:executeJSScript" + listenerId, executeJSScript);
addMessageListener("Marionette:setSearchTimeout" + listenerId, setSearchTimeout);
addMessageListener("Marionette:goUrl" + listenerId, goUrl);
addMessageListener("Marionette:getUrl" + listenerId, getUrl);
addMessageListener("Marionette:goBack" + listenerId, goBack);
addMessageListener("Marionette:goForward" + listenerId, goForward);
addMessageListener("Marionette:refresh" + listenerId, refresh);
addMessageListener("Marionette:findElementContent" + listenerId, findElementContent);
addMessageListener("Marionette:findElementsContent" + listenerId, findElementsContent);
addMessageListener("Marionette:clickElement" + listenerId, clickElement);
addMessageListener("Marionette:switchToFrame" + listenerId, switchToFrame);
addMessageListener("Marionette:deleteSession" + listenerId, deleteSession);
addMessageListener("Marionette:sleepSession" + listenerId, sleepSession);
}
/**
* Called when we start a new session. It registers the
* current environment, and resets all values
*/
function newSession(msg) {
isB2G = msg.json.B2G;
resetValues();
}
/**
* Puts the current session to sleep, so all listeners are removed except
* for the 'restart' listener. This is used to keep the content listener
* alive for reuse in B2G instead of reloading it each time.
*/
function sleepSession(msg) {
deleteSession();
addMessageListener("Marionette:restart", restart);
}
/**
* Restarts all our listeners after this listener was put to sleep
*/
function restart() {
removeMessageListener("Marionette:restart", restart);
registerSelf();
}
/**
* Removes all listeners
*/
function deleteSession(msg) {
removeMessageListener("Marionette:newSession" + listenerId, newSession);
removeMessageListener("Marionette:executeScript" + listenerId, executeScript);
removeMessageListener("Marionette:setScriptTimeout" + listenerId, setScriptTimeout);
removeMessageListener("Marionette:executeAsyncScript" + listenerId, executeAsyncScript);
removeMessageListener("Marionette:executeJSScript" + listenerId, executeJSScript);
removeMessageListener("Marionette:setSearchTimeout" + listenerId, setSearchTimeout);
removeMessageListener("Marionette:goUrl" + listenerId, goUrl);
removeMessageListener("Marionette:getUrl" + listenerId, getUrl);
removeMessageListener("Marionette:goBack" + listenerId, goBack);
removeMessageListener("Marionette:goForward" + listenerId, goForward);
removeMessageListener("Marionette:refresh" + listenerId, refresh);
removeMessageListener("Marionette:findElementContent" + listenerId, findElementContent);
removeMessageListener("Marionette:findElementsContent" + listenerId, findElementsContent);
removeMessageListener("Marionette:clickElement" + listenerId, clickElement);
removeMessageListener("Marionette:switchToFrame" + listenerId, switchToFrame);
removeMessageListener("Marionette:deleteSession" + listenerId, deleteSession);
removeMessageListener("Marionette:sleepSession" + listenerId, sleepSession);
this.elementManager.reset();
}
/*
* Helper methods
*/
/**
* Generic method to send a message to the server
*/
function sendToServer(msg, value, command_id) {
if (command_id) {
value.command_id = command_id;
}
sendAsyncMessage(msg, value);
}
/**
* Send response back to server
*/
function sendResponse(value, command_id) {
sendToServer("Marionette:done", value, command_id);
}
/**
* Send ack back to server
*/
function sendOk(command_id) {
sendToServer("Marionette:ok", {}, command_id);
}
/**
* Send log message to server
*/
function sendLog(msg) {
sendToServer("Marionette:log", { message: msg });
}
/**
* Send error message to server
*/
function sendError(message, status, trace, command_id) {
let error_msg = { message: message, status: status, stacktrace: trace };
sendToServer("Marionette:error", error_msg, command_id);
}
/**
* Clear test values after completion of test
*/
function resetValues() {
marionetteTimeout = null;
}
/**
* send error when we detect an unload event during async scripts
*/
function errUnload() {
sendError("unload was called", 17, null);
}
/*
* Marionette Methods
*/
/**
* Returns a content sandbox that can be used by the execute_foo functions.
*/
function createExecuteContentSandbox(aWindow, marionette, args) {
try {
args = elementManager.convertWrappedArguments(args, aWindow);
}
catch(e) {
sendError(e.message, e.num, e.stack);
return;
}
let sandbox = new Cu.Sandbox(aWindow);
sandbox.window = aWindow;
sandbox.document = sandbox.window.document;
sandbox.navigator = sandbox.window.navigator;
sandbox.__namedArgs = elementManager.applyNamedArgs(args);
sandbox.__marionetteParams = args;
sandbox.__proto__ = sandbox.window;
marionette.exports.forEach(function(fn) {
sandbox[fn] = marionette[fn].bind(marionette);
});
return sandbox;
}
/**
* Execute the given script either as a function body (executeScript)
* or directly (for 'mochitest' like JS Marionette tests)
*/
function executeScript(msg, directInject) {
let script = msg.json.value;
let marionette = new Marionette(false, win, "content", marionetteLogObj);
let sandbox = createExecuteContentSandbox(win, marionette, msg.json.args);
if (!sandbox)
return;
sandbox.finish = function sandbox_finish() {
return marionette.generate_results();
};
try {
if (directInject) {
let res = Cu.evalInSandbox(script, sandbox, "1.8");
sendSyncMessage("Marionette:testLog", {value: elementManager.wrapValue(marionetteLogObj.getLogs())});
marionetteLogObj.clearLogs();
if (res == undefined || res.passed == undefined) {
sendError("Marionette.finish() not called", 17, null);
}
else {
sendResponse({value: elementManager.wrapValue(res)});
}
}
else {
let scriptSrc = "let __marionetteFunc = function(){" + script + "};" +
"__marionetteFunc.apply(null, __marionetteParams);";
let res = Cu.evalInSandbox(scriptSrc, sandbox, "1.8");
sendSyncMessage("Marionette:testLog", {value: elementManager.wrapValue(marionetteLogObj.getLogs())});
marionetteLogObj.clearLogs();
sendResponse({value: elementManager.wrapValue(res)});
}
}
catch (e) {
// 17 = JavascriptException
sendError(e.name + ': ' + e.message, 17, e.stack);
}
}
/**
* Function to set the timeout of asynchronous scripts
*/
function setScriptTimeout(msg) {
marionetteTimeout = msg.json.value;
}
/**
* Execute async script
*/
function executeAsyncScript(msg) {
executeWithCallback(msg);
}
/**
* Execute pure JS test. Handles both async and sync cases.
*/
function executeJSScript(msg) {
if (msg.json.timeout) {
executeWithCallback(msg, msg.json.timeout);
}
else {
executeScript(msg, true);
}
}
/**
* This function is used by executeAsync and executeJSScript to execute a script
* in a sandbox.
*
* For executeJSScript, it will return a message only when the finish() method is called.
* For executeAsync, it will return a response when marionetteScriptFinished/arguments[arguments.length-1]
* method is called, or if it times out.
*/
function executeWithCallback(msg, timeout) {
win.addEventListener("unload", errUnload, false);
let script = msg.json.value;
let command_id = msg.json.id;
// Error code 28 is scriptTimeout, but spec says execute_async should return 21 (Timeout),
// see http://code.google.com/p/selenium/wiki/JsonWireProtocol#/session/:sessionId/execute_async.
// However Selenium code returns 28, see
// http://code.google.com/p/selenium/source/browse/trunk/javascript/firefox-driver/js/evaluate.js.
// We'll stay compatible with the Selenium code.
let timeoutId = win.setTimeout(function() {
contentAsyncReturnFunc('timed out', 28);
}, marionetteTimeout);
win.addEventListener('error', function win__onerror(evt) {
win.removeEventListener('error', win__onerror, true);
contentAsyncReturnFunc(evt, 17);
return true;
}, true);
function contentAsyncReturnFunc(value, status) {
win.removeEventListener("unload", errUnload, false);
/* clear all timeouts potentially generated by the script*/
for(let i=0; i<=timeoutId; i++) {
win.clearTimeout(i);
}
sendSyncMessage("Marionette:testLog", {value: elementManager.wrapValue(marionetteLogObj.getLogs())});
marionetteLogObj.clearLogs();
if (status == 0){
sendResponse({value: elementManager.wrapValue(value), status: status}, command_id);
}
else {
sendError(value, status, null, command_id);
}
};
let scriptSrc;
if (timeout) {
if (marionetteTimeout == null || marionetteTimeout == 0) {
sendError("Please set a timeout", 21, null);
}
scriptSrc = script;
}
else {
scriptSrc = "let marionetteScriptFinished = function(value) { return asyncComplete(value,0);};" +
"__marionetteParams.push(marionetteScriptFinished);" +
"let __marionetteFunc = function() { " + script + "};" +
"__marionetteFunc.apply(null, __marionetteParams); ";
}
let marionette = new Marionette(true, win, "content", marionetteLogObj);
let sandbox = createExecuteContentSandbox(win, marionette, msg.json.args);
if (!sandbox)
return;
sandbox.asyncComplete = contentAsyncReturnFunc;
sandbox.finish = function sandbox_finish() {
contentAsyncReturnFunc(marionette.generate_results(), 0);
};
try {
Cu.evalInSandbox(scriptSrc, sandbox, "1.8");
} catch (e) {
// 17 = JavascriptException
sendError(e.name + ': ' + e.message, 17, e.stack);
}
}
/**
* Function to set the timeout period for element searching
*/
function setSearchTimeout(msg) {
try {
elementManager.setSearchTimeout(msg.json.value);
}
catch (e) {
sendError(e.message, e.num, e.stack);
return;
}
sendOk();
}
/**
* Navigate to URI. Handles the case where we navigate within an iframe.
* All other navigation is handled by the server (in chrome space).
*/
function goUrl(msg) {
if (activeFrame != null) {
win.document.location = msg.json.value;
//TODO: replace this with event firing when Bug 720714 is resolved
let checkTimer = Components.classes["@mozilla.org/timer;1"].createInstance(Components.interfaces.nsITimer);
let checkLoad = function () {
if (win.document.readyState == "complete") {
sendOk();
}
else {
checkTimer.initWithCallback(checkLoad, 100, Components.interfaces.nsITimer.TYPE_ONE_SHOT);
}
};
checkLoad();
}
else {
sendAsyncMessage("Marionette:goUrl", {value: msg.json.value});
}
}
/**
* Get the current URI
*/
function getUrl(msg) {
sendResponse({value: win.location.href});
}
/**
* Go back in history
*/
function goBack(msg) {
win.history.back();
sendOk();
}
/**
* Go forward in history
*/
function goForward(msg) {
win.history.forward();
sendOk();
}
/**
* Refresh the page
*/
function refresh(msg) {
win.location.reload(true);
let listen = function() { removeEventListener("DOMContentLoaded", arguments.callee, false); sendOk() } ;
addEventListener("DOMContentLoaded", listen, false);
}
/**
* Find an element in the document using requested search strategy
*/
function findElementContent(msg) {
//Todo: extend to support findChildElement
let id;
try {
let notify = function(id) { sendResponse({value:id});};
id = elementManager.find(msg.json, win.document, notify, false);
}
catch (e) {
sendError(e.message, e.num, e.stack);
return;
}
}
/**
* Find elements in the document using requested search strategy
*/
function findElementsContent(msg) {
//Todo: extend to support findChildElement
let id;
try {
let notify = function(id) { sendResponse({value:id});};
id = elementManager.find(msg.json, win.document, notify, true);
}
catch (e) {
sendError(e.message, e.num, e.stack);
return;
}
}
/**
* Send click event to element
*/
function clickElement(msg) {
let el;
try {
el = elementManager.getKnownElement(msg.json.element, win);
}
catch (e) {
sendError(e.message, e.num, e.stack);
return;
}
el.click();
sendOk();
}
/**
* Switch to frame given either the server-assigned element id,
* its index in window.frames, or the iframe's name or id.
*/
function switchToFrame(msg) {
let foundFrame = null;
if ((msg.json.value == null) && (msg.json.element == null)) {
win = content;
activeFrame = null;
content.focus();
sendOk();
return;
}
if (msg.json.element != undefined) {
if (elementManager.seenItems[msg.json.element] != undefined) {
let wantedFrame = elementManager.getKnownElement(msg.json.element, win);//HTMLIFrameElement
let numFrames = win.frames.length;
for (let i = 0; i < numFrames; i++) {
if (win.frames[i].frameElement == wantedFrame) {
win = win.frames[i];
activeFrame = i;
win.focus();
sendOk();
return;
}
}
}
}
switch(typeof(msg.json.value)) {
case "string" :
let foundById = null;
let numFrames = win.frames.length;
for (let i = 0; i < numFrames; i++) {
//give precedence to name
let frame = win.frames[i];
let frameElement = frame.frameElement;
if (frameElement.name == msg.json.value) {
foundFrame = i;
break;
} else if ((foundById == null) && (frameElement.id == msg.json.value)) {
foundById = i;
}
}
if ((foundFrame == null) && (foundById != null)) {
foundFrame = foundById;
}
break;
case "number":
if (win.frames[msg.json.value] != undefined) {
foundFrame = msg.json.value;
}
break;
}
//TODO: implement index
if (foundFrame != null) {
let frameWindow = win.frames[foundFrame];
activeFrame = foundFrame;
win = frameWindow;
win.focus();
sendOk();
} else {
sendError("Unable to locate frame: " + msg.json.value, 8, null);
}
}
//call register self when we get loaded
registerSelf();

View File

@ -0,0 +1,45 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
function MarionetteLogObj() {
this.logs = [];
}
MarionetteLogObj.prototype = {
/**
* Log message. Accepts user defined log-level.
* @param msg String
* The message to be logged
* @param level String
* The logging level to be used
*/
log: function ML_log(msg, level) {
let lev = level ? level : "INFO";
this.logs.push( [lev, msg, (new Date()).toString()]);
},
/**
* Add a list of logs to its list
* @param msgs Object
* Takes a list of strings
*/
addLogs: function ML_addLogs(msgs) {
for (let i = 0; i < msgs.length; i++) {
this.logs.push(msgs[i]);
}
},
/**
* Return all logged messages.
*/
getLogs: function ML_getLogs() {
return this.logs;
},
/**
* Clears the logs
*/
clearLogs: function ML_clearLogs() {
this.logs = [];
},
}

View File

@ -0,0 +1,131 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
/*
* The Marionette object, passed to the script context.
*/
function Marionette(is_async, window, context, logObj) {
this.is_async = is_async;
this.window = window;
this.tests = [];
this.logObj = logObj;
this.context = context;
this.exports = ['ok', 'is', 'isnot', 'log', 'getLogs', 'generate_results', 'waitFor'];
}
Marionette.prototype = {
ok: function Marionette__ok(condition, name, diag) {
let test = {'result': !!condition, 'name': name, 'diag': diag};
this.logResult(test, "TEST-PASS", "TEST-UNEXPECTED-FAIL");
this.tests.push(test);
},
is: function Marionette__is(a, b, name) {
let pass = (a == b);
let diag = pass ? this.repr(a) + " should equal " + this.repr(b)
: "got " + this.repr(a) + ", expected " + this.repr(b);
this.ok(pass, name, diag);
},
isnot: function Marionette__isnot (a, b, name) {
let pass = (a != b);
let diag = pass ? this.repr(a) + " should not equal " + this.repr(b)
: "didn't expect " + this.repr(a) + ", but got it";
this.ok(pass, name, diag);
},
log: function Marionette__log(msg, level) {
if (this.logObj != null) {
this.logObj.log(msg, level);
}
},
getLogs: function Marionette__getLogs() {
if (this.logObj != null) {
this.logObj.getLogs();
}
},
generate_results: function Marionette__generate_results() {
let passed = 0;
let failed = 0;
let failures = [];
for (let i in this.tests) {
if(this.tests[i].result) {
passed++;
}
else {
failed++;
failures.push({'name': this.tests[i].name,
'diag': this.tests[i].diag});
}
}
return {"passed": passed, "failed": failed, "failures": failures};
},
logToFile: function Marionette__logToFile(file) {
//TODO
},
logResult: function Marionette__logResult(test, passString, failString) {
//TODO: dump to file
let resultString = test.result ? passString : failString;
let diagnostic = test.name + (test.diag ? " - " + test.diag : "");
let msg = [resultString, diagnostic].join(" | ");
dump("MARIONETTE TEST RESULT:" + msg + "\n");
},
repr: function Marionette__repr(o) {
if (typeof(o) == "undefined") {
return "undefined";
} else if (o === null) {
return "null";
}
try {
if (typeof(o.__repr__) == 'function') {
return o.__repr__();
} else if (typeof(o.repr) == 'function' && o.repr != arguments.callee) {
return o.repr();
}
} catch (e) {
}
try {
if (typeof(o.NAME) == 'string' && (
o.toString == Function.prototype.toString ||
o.toString == Object.prototype.toString
)) {
return o.NAME;
}
} catch (e) {
}
let ostring;
try {
ostring = (o + "");
} catch (e) {
return "[" + typeof(o) + "]";
}
if (typeof(o) == "function") {
o = ostring.replace(/^\s+/, "");
let idx = o.indexOf("{");
if (idx != -1) {
o = o.substr(0, idx) + "{...}";
}
}
return ostring;
},
defaultWaitForTimeout: 10000,
waitFor: function test_waitFor(callback, test, timeout) {
if (test()) {
callback();
return;
}
timeout = timeout || Date.now();
if (Date.now() - timeout > this.defaultWaitForTimeout) {
throw 'waitFor timeout';
}
this.window.setTimeout(this.waitFor.bind(this), 100, callback, test, timeout);
},
};

View File

@ -0,0 +1,17 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this file,
# You can obtain one at http://mozilla.org/MPL/2.0/.
DEPTH = ../../..
topsrcdir = @top_srcdir@
srcdir = @srcdir@
VPATH = @srcdir@
relativesrcdir = testing/marionette/tests
include $(DEPTH)/config/autoconf.mk
MODULE = test_marionette
XPCSHELL_TESTS = unit
include $(topsrcdir)/config/rules.mk

View File

@ -0,0 +1,11 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
const Cc = Components.classes;
const Ci = Components.interfaces;
const Cu = Components.utils;
const Cr = Components.results;
Cu.import("resource:///modules/devtools/dbg-server.jsm");
Cu.import("resource:///modules/devtools/dbg-client.jsm");

View File

@ -0,0 +1,53 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
function run_test()
{
//DebuggerServer.addActors("resource:///modules/marionette-actors.js");
//DebuggerServer.init();
add_test(test_error);
run_next_test();
}
function test_error()
{
//DebuggerServer.openListener(2828, true);
do_test_pending();
received = false;
id = "";
let transport = debuggerSocketConnect("127.0.0.1", 2828);
transport.hooks = {
onPacket: function(aPacket) {
this.onPacket = function(aPacket) {
if (received) {
do_check_eq(aPacket.from, id);
if(aPacket.error == undefined) {
do_throw("Expected error, instead received 'done' packet!");
transport.close();
}
else {
transport.close();
}
}
else {
received = true;
id = aPacket.id;
transport.send({to: id,
type: "nonExistent",
});
}
}
transport.send({to: "root",
type: "getMarionetteID",
});
},
onClosed: function(aStatus) {
do_check_eq(aStatus, 0);
do_test_finished();
run_next_test();
},
};
transport.ready();
}

View File

@ -0,0 +1,75 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
function run_test()
{
//DebuggerServer.addActors("resource:///modules/marionette-actors.js");
//DebuggerServer.init();
add_test(test_execute);
run_next_test();
}
function test_execute()
{
//DebuggerServer.openListener(2828, true);
do_test_pending();
got_session = false;
received = false;
id = "";
let transport = debuggerSocketConnect("127.0.0.1", 2828);
transport.hooks = {
onPacket: function(aPacket) {
this.onPacket = function(aPacket) {
if(!got_session) {
got_session=true;
id = aPacket.id;
transport.send({to: id,
type: "newSession",
});
}
else {
if (received) {
do_check_eq(aPacket.from, id);
if(aPacket.value == "3") {
transport.send({to: id,
type: "executeScript",
value: "return 5+arguments[0];",
args: [1],
});
}
if(aPacket.value == "6") {
transport.send({to: id,
type: "deleteSession"
});
transport.close();
}
if(aPacket.error != undefined) {
do_throw("Received error: " + aPacket.error);
transport.close();
}
}
else {
received = true;
do_check_eq('session', aPacket.value);
transport.send({to: id,
type: "executeScript",
value: "alert('asdf'); return 2+1;",
});
}
}
}
transport.send({to: "root",
type: "getMarionetteID",
});
},
onClosed: function(aStatus) {
do_check_eq(aStatus, 0);
do_test_finished();
run_next_test();
delete transport;
},
};
transport.ready();
}

View File

@ -0,0 +1,193 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
function run_test()
{
add_test(test_executeAsync);
add_test(test_executeAsyncTimeout);
add_test(test_executeAsyncUnload); //TODO: fix unload listener
run_next_test();
}
function test_executeAsync()
{
do_test_pending();
got_session = false;
received = false;
id = "";
let transport = debuggerSocketConnect("127.0.0.1", 2828);
transport.hooks = {
onPacket: function(aPacket) {
this.onPacket = function(aPacket) {
if(!got_session) {
got_session=true;
id = aPacket.id;
transport.send({to: id,
type: "newSession",
});
}
else {
if (received) {
do_check_eq(aPacket.from, id);
if(aPacket.ok == true) {
transport.send({to: id,
type: "executeAsyncScript",
value: "arguments[arguments.length - 1](5+1);",
});
}
else if(aPacket.value == "6") {
transport.send({to: id,
type: "deleteSession",
});
transport.close();
}
else if(aPacket.error != undefined) {
do_throw("Received error: " + aPacket.error);
transport.close();
}
}
else {
received = true;
do_check_eq('session', aPacket.value);
transport.send({to: id,
type: "setScriptTimeout",
value: "2000",
});
}
}
}
transport.send({to: "root",
type: "getMarionetteID",
});
},
onClosed: function(aStatus) {
do_check_eq(aStatus, 0);
do_test_finished();
delete transport;
run_next_test();
},
};
transport.ready();
}
function test_executeAsyncTimeout()
{
do_test_pending();
got_session = false;
received = false;
id = "";
let transport = debuggerSocketConnect("127.0.0.1", 2828);
transport.hooks = {
onPacket: function(aPacket) {
this.onPacket = function(aPacket) {
if(!got_session) {
got_session=true;
id = aPacket.id;
transport.send({to: id,
type: "newSession",
});
}
else {
if (received) {
do_check_eq(aPacket.from, id);
if(aPacket.ok == true) {
transport.send({to: id,
type: "executeAsyncScript",
value: "window.setTimeout(arguments[arguments.length - 1], 5000, 6);",
});
}
else if(aPacket.value == "6") {
do_throw("Should have timed out!");
transport.close();
}
else if(aPacket.error != undefined) {
do_check_eq(aPacket.error.message, "timed out");
do_check_eq(aPacket.error.status, 28);
transport.close();
}
}
else {
received = true;
do_check_eq('session', aPacket.value);
transport.send({to: id,
type: "setScriptTimeout",
value: "2000",
});
}
}
}
transport.send({to: "root",
type: "getMarionetteID",
});
},
onClosed: function(aStatus) {
do_check_eq(aStatus, 0);
do_test_finished();
delete transport;
run_next_test();
},
};
transport.ready();
}
function test_executeAsyncUnload()
{
do_test_pending();
got_session = false;
received = false;
id = "";
let transport = debuggerSocketConnect("127.0.0.1", 2828);
transport.hooks = {
onPacket: function(aPacket) {
this.onPacket = function(aPacket) {
if(!got_session) {
got_session=true;
id = aPacket.id;
transport.send({to: id,
type: "newSession",
});
}
else {
if (received) {
do_check_eq(aPacket.from, id);
if(aPacket.ok == true) {
transport.send({to: id,
type: "executeAsyncScript",
value: "window.location.reload();",
});
}
else if(aPacket.value == "6") {
do_throw("Should have thrown unload error!");
transport.close();
}
else if(aPacket.error != undefined) {
do_check_eq(aPacket.error.status, 17);
transport.close();
}
}
else {
received = true;
do_check_eq('session', aPacket.value);
transport.send({to: id,
type: "setScriptTimeout",
value: "2000",
});
}
}
}
transport.send({to: "root",
type: "getMarionetteID",
});
},
onClosed: function(aStatus) {
do_check_eq(aStatus, 0);
do_test_finished();
delete transport;
run_next_test();
},
};
transport.ready();
}

View File

@ -0,0 +1,365 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
function run_test()
{
add_test(test_execute);
add_test(test_execute_async);
add_test(test_execute_async_timeout);
add_test(test_execute_chrome);
add_test(test_execute_async_chrome);
add_test(test_execute_async_timeout_chrome);
run_next_test();
}
function test_execute()
{
do_test_pending();
got_session = false;
received = false;
id = "";
let transport = debuggerSocketConnect("127.0.0.1", 2828);
transport.hooks = {
onPacket: function(aPacket) {
this.onPacket = function(aPacket) {
if(!got_session) {
got_session=true;
id = aPacket.id;
transport.send({to: id,
type: "newSession",
});
}
else {
if (received) {
do_check_eq(aPacket.from, id);
do_check_eq(aPacket.value.passed, 1);
do_check_eq(aPacket.value.failed, 0);
transport.close();
}
else {
received = true;
do_check_eq('mobile', aPacket.value);
transport.send({to: id,
type: "executeJSScript",
value: "Marionette.is(1,1, 'should return 1'); Marionette.finish();",
timeout: false
});
}
}
}
transport.send({to: "root",
type: "getMarionetteID",
});
},
onClosed: function(aStatus) {
do_check_eq(aStatus, 0);
do_test_finished();
run_next_test();
delete transport;
},
};
transport.ready();
}
function test_execute_async()
{
do_test_pending();
got_session = false;
received = false;
received2 = false;
id = "";
let transport = debuggerSocketConnect("127.0.0.1", 2828);
transport.hooks = {
onPacket: function(aPacket) {
this.onPacket = function(aPacket) {
if(!got_session) {
got_session=true;
id = aPacket.id;
transport.send({to: id,
type: "newSession",
});
}
else {
if (received) {
if(received2) {
do_check_eq(aPacket.from, id);
do_check_eq(aPacket.value.passed, 1);
do_check_eq(aPacket.value.failed, 0);
transport.close();
}
else {
received2 = true;
transport.send({to: id,
type: "executeJSScript",
value: "Marionette.is(1,1, 'should return 1'); Marionette.finish();",
timeout: true
});
}
}
else {
received = true;
do_check_eq('mobile', aPacket.value);
transport.send({to: id,
type: "setScriptTimeout",
value: "2000",
});
}
}
}
transport.send({to: "root",
type: "getMarionetteID",
});
},
onClosed: function(aStatus) {
do_check_eq(aStatus, 0);
do_test_finished();
run_next_test();
delete transport;
},
};
transport.ready();
}
function test_execute_async_timeout()
{
do_test_pending();
got_session = false;
received = false;
received2 = false;
id = "";
let transport = debuggerSocketConnect("127.0.0.1", 2828);
transport.hooks = {
onPacket: function(aPacket) {
this.onPacket = function(aPacket) {
if(!got_session) {
got_session=true;
id = aPacket.id;
transport.send({to: id,
type: "newSession",
});
}
else {
if (received) {
if(received2) {
do_check_eq(aPacket.from, id);
do_check_eq(aPacket.error.status, 28);
transport.close();
}
else {
received2 = true;
transport.send({to: id,
type: "executeJSScript",
value: "Marionette.is(1,1, 'should return 1');",
timeout: true
});
}
}
else {
received = true;
do_check_eq('mobile', aPacket.value);
transport.send({to: id,
type: "setScriptTimeout",
value: "2000",
});
}
}
}
transport.send({to: "root",
type: "getMarionetteID",
});
},
onClosed: function(aStatus) {
do_check_eq(aStatus, 0);
do_test_finished();
run_next_test();
delete transport;
},
};
transport.ready();
}
function test_execute_chrome()
{
do_test_pending();
got_session = false;
got_context = false;
received = false;
id = "";
let transport = debuggerSocketConnect("127.0.0.1", 2828);
transport.hooks = {
onPacket: function(aPacket) {
this.onPacket = function(aPacket) {
if(!got_session) {
got_session=true;
id = aPacket.id;
transport.send({to: id,
type: "newSession",
});
}
else if (!got_context) {
got_context = true;
do_check_eq('mobile', aPacket.value);
transport.send({to: id,
type: "setContext",
value: "chrome",
});
}
else if (!received) {
received = true;
transport.send({to: id,
type: "executeJSScript",
value: "Marionette.is(1,1, 'should return 1'); Marionette.finish();",
timeout: false
});
}
else {
do_check_eq(aPacket.from, id);
do_check_eq(aPacket.value.passed, 1);
do_check_eq(aPacket.value.failed, 0);
transport.close();
}
}
transport.send({to: "root",
type: "getMarionetteID",
});
},
onClosed: function(aStatus) {
do_check_eq(aStatus, 0);
do_test_finished();
run_next_test();
delete transport;
},
};
transport.ready();
}
function test_execute_async_chrome()
{
do_test_pending();
got_session = false;
got_context = false;
received = false;
received2 = false;
id = "";
let transport = debuggerSocketConnect("127.0.0.1", 2828);
transport.hooks = {
onPacket: function(aPacket) {
this.onPacket = function(aPacket) {
if(!got_session) {
got_session=true;
id = aPacket.id;
transport.send({to: id,
type: "newSession",
});
}
else if (!got_context) {
got_context = true;
do_check_eq('mobile', aPacket.value);
transport.send({to: id,
type: "setContext",
value: "chrome",
});
}
else if (!received) {
received = true;
transport.send({to: id,
type: "setScriptTimeout",
value: "2000",
});
}
else if (!received2) {
received2 = true;
transport.send({to: id,
type: "executeJSScript",
value: "Marionette.is(1,1, 'should return 1'); Marionette.finish();",
timeout: true
});
}
else {
do_check_eq(aPacket.from, id);
do_check_eq(aPacket.value.passed, 1);
do_check_eq(aPacket.value.failed, 0);
transport.close();
}
}
transport.send({to: "root",
type: "getMarionetteID",
});
},
onClosed: function(aStatus) {
do_check_eq(aStatus, 0);
do_test_finished();
run_next_test();
delete transport;
},
};
transport.ready();
}
function test_execute_async_timeout_chrome()
{
do_test_pending();
got_session = false;
got_context = false;
received = false;
received2 = false;
id = "";
let transport = debuggerSocketConnect("127.0.0.1", 2828);
transport.hooks = {
onPacket: function(aPacket) {
this.onPacket = function(aPacket) {
if(!got_session) {
got_session=true;
id = aPacket.id;
transport.send({to: id,
type: "newSession",
});
}
else if (!got_context) {
got_context = true;
do_check_eq('mobile', aPacket.value);
transport.send({to: id,
type: "setContext",
value: "chrome",
});
}
else if (!received) {
received = true;
transport.send({to: id,
type: "setScriptTimeout",
value: "2000",
});
}
else if (!received2) {
received2 = true;
transport.send({to: id,
type: "executeJSScript",
value: "Marionette.is(1,1, 'should return 1');",
timeout: true
});
}
else {
do_check_eq(aPacket.from, id);
do_check_eq(aPacket.error.status, 28);
transport.close();
}
}
transport.send({to: "root",
type: "getMarionetteID",
});
},
onClosed: function(aStatus) {
do_check_eq(aStatus, 0);
do_test_finished();
run_next_test();
delete transport;
},
};
transport.ready();
}

View File

@ -0,0 +1,8 @@
[DEFAULT]
head = head_mar.js
tail =
[test_marionette_exec.js]
[test_marionette_execjs.js]
[test_marionette_execAsync.js]
[test_marionette_err.js]

View File

@ -46,6 +46,7 @@ skip-if = os == "android"
[include:toolkit/mozapps/update/test_svc/unit/xpcshell.ini] [include:toolkit/mozapps/update/test_svc/unit/xpcshell.ini]
[include:toolkit/mozapps/update/test/unit/xpcshell.ini] [include:toolkit/mozapps/update/test/unit/xpcshell.ini]
[include:security/manager/ssl/tests/unit/xpcshell.ini] [include:security/manager/ssl/tests/unit/xpcshell.ini]
[include:testing/marionette/tests/unit/xpcshell.ini]
[include:testing/xpcshell/example/unit/xpcshell.ini] [include:testing/xpcshell/example/unit/xpcshell.ini]
[include:xpcom/tests/unit/xpcshell.ini] [include:xpcom/tests/unit/xpcshell.ini]
[include:modules/libpref/test/unit/xpcshell.ini] [include:modules/libpref/test/unit/xpcshell.ini]

View File

@ -273,6 +273,10 @@ ifdef MOZ_MAPINFO
tier_platform_dirs += tools/codesighs tier_platform_dirs += tools/codesighs
endif endif
ifdef ENABLE_MARIONETTE
tier_platform_dirs += testing/marionette
endif
ifdef ENABLE_TESTS ifdef ENABLE_TESTS
tier_platform_dirs += testing/mochitest tier_platform_dirs += testing/mochitest
tier_platform_dirs += testing/xpcshell tier_platform_dirs += testing/xpcshell