mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
Bug 712643 - land Marionette on m-c. r=mossop,robcee
This commit is contained in:
parent
883f765d75
commit
9ebdd062f8
@ -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
|
||||||
|
@ -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);
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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@
|
||||||
|
@ -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)
|
||||||
|
19
testing/marionette/Makefile.in
Normal file
19
testing/marionette/Makefile.in
Normal 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
|
17
testing/marionette/components/Makefile.in
Normal file
17
testing/marionette/components/Makefile.in
Normal 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
|
@ -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
|
89
testing/marionette/components/marionettecomponent.js
Normal file
89
testing/marionette/components/marionettecomponent.js
Normal 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]);
|
7
testing/marionette/jar.mn
Normal file
7
testing/marionette/jar.mn
Normal 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)
|
1050
testing/marionette/marionette-actors.js
Normal file
1050
testing/marionette/marionette-actors.js
Normal file
File diff suppressed because it is too large
Load Diff
403
testing/marionette/marionette-elements.js
Normal file
403
testing/marionette/marionette-elements.js
Normal 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);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
534
testing/marionette/marionette-listener.js
Normal file
534
testing/marionette/marionette-listener.js
Normal 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();
|
45
testing/marionette/marionette-log-obj.js
Normal file
45
testing/marionette/marionette-log-obj.js
Normal 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 = [];
|
||||||
|
},
|
||||||
|
}
|
131
testing/marionette/marionette-simpletest.js
Normal file
131
testing/marionette/marionette-simpletest.js
Normal 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);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
17
testing/marionette/tests/Makefile.in
Normal file
17
testing/marionette/tests/Makefile.in
Normal 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
|
11
testing/marionette/tests/unit/head_mar.js
Normal file
11
testing/marionette/tests/unit/head_mar.js
Normal 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");
|
53
testing/marionette/tests/unit/test_marionette_err.js
Normal file
53
testing/marionette/tests/unit/test_marionette_err.js
Normal 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();
|
||||||
|
}
|
75
testing/marionette/tests/unit/test_marionette_exec.js
Normal file
75
testing/marionette/tests/unit/test_marionette_exec.js
Normal 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();
|
||||||
|
}
|
193
testing/marionette/tests/unit/test_marionette_execAsync.js
Normal file
193
testing/marionette/tests/unit/test_marionette_execAsync.js
Normal 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();
|
||||||
|
}
|
365
testing/marionette/tests/unit/test_marionette_execjs.js
Normal file
365
testing/marionette/tests/unit/test_marionette_execjs.js
Normal 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();
|
||||||
|
}
|
8
testing/marionette/tests/unit/xpcshell.ini
Normal file
8
testing/marionette/tests/unit/xpcshell.ini
Normal 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]
|
@ -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]
|
||||||
|
@ -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
|
||||||
|
Loading…
Reference in New Issue
Block a user