Bug 754216 - Control the emulator from within Marionette JS scripts. r=jgriffin DONTBUILD because NPOTB
authorPhilipp von Weitershausen <philipp@weitershausen.de>
Fri, 18 May 2012 13:30:43 -0700
changeset 94360 9dab33fa5ff4aecbc36dfd6c09027fee897cf05e
parent 94359 921706236f86b85e1df48757963ddc87a98d0cb9
child 94417 642d1a36702f7fb3f34dd3a4220adf6a5a257fa9
child 94422 b292b2847b7f1de280c57384a2426dd0e25455b3
push id22711
push userpweitershausen@mozilla.com
push dateFri, 18 May 2012 20:31:04 +0000
treeherdermozilla-central@9dab33fa5ff4 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjgriffin
bugs754216
milestone15.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 754216 - Control the emulator from within Marionette JS scripts. r=jgriffin DONTBUILD because NPOTB
testing/marionette/client/marionette/marionette.py
testing/marionette/client/marionette/marionette_test.py
testing/marionette/client/marionette/tests/unit/test_emulator.py
testing/marionette/client/marionette/tests/unit/test_simpletest_pass.js
testing/marionette/client/marionette/tests/unit/unit-tests.ini
testing/marionette/client/marionette/www/empty.html
testing/marionette/client/marionette/www/test.html
testing/marionette/marionette-actors.js
testing/marionette/marionette-listener.js
testing/marionette/marionette-simpletest.js
--- a/testing/marionette/client/marionette/marionette.py
+++ b/testing/marionette/client/marionette/marionette.py
@@ -164,21 +164,37 @@ class Marionette(object):
             self.window = None
             self.client.close()
             if self.emulator:
                 port = self.emulator.restart(self.local_port)
                 if port is not None:
                     self.port = self.client.port = port
             raise TimeoutException(message='socket.timeout', status=21, stacktrace=None)
 
+        # Process any emulator commands that are sent from a script
+        # while it's executing.
+        while response.get("emulator_cmd"):
+            response = self._handle_emulator_cmd(response)
+
         if (response_key == 'ok' and response.get('ok') ==  True) or response_key in response:
             return response[response_key]
         else:
             self._handle_error(response)
 
+    def _handle_emulator_cmd(self, response):
+        cmd = response.get("emulator_cmd")
+        if not cmd or not self.emulator:
+            raise MarionetteException(message="No emulator in this test to run "
+                                      "command against.")
+        cmd = cmd.encode("ascii")
+        result = self.emulator._run_telnet(cmd)
+        return self.client.send({"type": "emulatorCmdResult",
+                                 "id": response.get("id"),
+                                 "result": result})
+
     def _handle_error(self, response):
         if 'error' in response and isinstance(response['error'], dict):
             status = response['error'].get('status', 500)
             message = response['error'].get('message')
             stacktrace = response['error'].get('stacktrace')
             # status numbers come from 
             # http://code.google.com/p/selenium/wiki/JsonWireProtocol#Response_Status_Codes
             if status == 7:
--- a/testing/marionette/client/marionette/marionette_test.py
+++ b/testing/marionette/client/marionette/marionette_test.py
@@ -146,16 +146,20 @@ class MarionetteJSTestCase(CommonTestCas
                 else:
                     js += line
 
         context = self.context_re.search(js)
         if context:
             context = context.group(3)
             self.marionette.set_context(context)
 
+        if context != "chrome":
+            page = self.marionette.absolute_url("empty.html")
+            self.marionette.navigate(page)
+
         timeout = self.timeout_re.search(js)
         if timeout:
             timeout = timeout.group(3)
             self.marionette.set_script_timeout(timeout)
 
         launch_app = self.launch_re.search(js)
         if launch_app:
             launch_app = launch_app.group(3)
new file mode 100644
--- /dev/null
+++ b/testing/marionette/client/marionette/tests/unit/test_emulator.py
@@ -0,0 +1,20 @@
+from marionette_test import MarionetteTestCase
+from errors import JavascriptException, MarionetteException
+
+class TestEmulatorContent(MarionetteTestCase):
+
+    def test_emulator_cmd(self):
+        self.marionette.set_script_timeout(10000)
+        expected = ["gsm voice state: home",
+                    "gsm data state:  home",
+                    "OK"]
+        result = self.marionette.execute_async_script("""
+        runEmulatorCmd("gsm status", marionetteScriptFinished)
+        """);
+        self.assertEqual(result, expected)
+
+class TestEmulatorChrome(TestEmulatorContent):
+
+    def setUp(self):
+        super(TestEmulatorChrome, self).setUp()
+        self.marionette.set_context("chrome")
--- a/testing/marionette/client/marionette/tests/unit/test_simpletest_pass.js
+++ b/testing/marionette/client/marionette/tests/unit/test_simpletest_pass.js
@@ -2,10 +2,13 @@
  * 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/. */
 
 MARIONETTE_TIMEOUT = 1000;
 
 is(2, 2, "test for is()");
 isnot(2, 3, "test for isnot()");
 ok(2 == 2, "test for ok()");
+
+is(window.location.pathname.slice(-10), "empty.html");
+
 setTimeout(finish, 100);
 
--- a/testing/marionette/client/marionette/tests/unit/unit-tests.ini
+++ b/testing/marionette/client/marionette/tests/unit/unit-tests.ini
@@ -5,16 +5,17 @@ b2g = false
 [test_getattr.py]
 b2g = false
 [test_elementState.py]
 b2g = false
 [test_text.py]
 b2g = false
 
 [test_log.py]
+[test_emulator.py]
 [test_execute_async_script.py]
 [test_execute_script.py]
 [test_simpletest_fail.js]
 [test_findelement.py]
 b2g = false
 
 [test_navigation.py]
 b2g = false
new file mode 100644
--- /dev/null
+++ b/testing/marionette/client/marionette/www/empty.html
@@ -0,0 +1,8 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>Marionette Test</title>
+</head>
+<body>
+</body>
+</html> 
--- a/testing/marionette/client/marionette/www/test.html
+++ b/testing/marionette/client/marionette/www/test.html
@@ -1,9 +1,9 @@
-<!doctype html>
+<!DOCTYPE html>
 <html>
 <head>
 <title>Marionette Test</title>
 </head>
 <body>
   <h1 id="testh1">Test Page</h1>
   <script type="text/javascript">
     window.ready = true;
--- a/testing/marionette/marionette-actors.js
+++ b/testing/marionette/marionette-actors.js
@@ -126,16 +126,17 @@ function MarionetteDriverActor(aConnecti
   //register all message listeners
   this.messageManager.addMessageListener("Marionette:ok", this);
   this.messageManager.addMessageListener("Marionette:done", this);
   this.messageManager.addMessageListener("Marionette:error", this);
   this.messageManager.addMessageListener("Marionette:log", this);
   this.messageManager.addMessageListener("Marionette:testLog", this);
   this.messageManager.addMessageListener("Marionette:register", this);
   this.messageManager.addMessageListener("Marionette:goUrl", this);
+  this.messageManager.addMessageListener("Marionette:runEmulatorCmd", this);
 }
 
 MarionetteDriverActor.prototype = {
 
   //name of the actor
   actorPrefix: "marionette",
 
   /**
@@ -160,18 +161,23 @@ MarionetteDriverActor.prototype = {
    * @param object msg
    *        Response to send back to client
    * @param string command_id
    *        Unique identifier assigned to the client's request.
    *        Used to distinguish the asynchronous responses.
    */
   sendToClient: function MDA_sendToClient(msg, command_id) {
     logger.info("sendToClient: " + JSON.stringify(msg) + ", " + command_id + ", " + this.command_id);
-    if (command_id == undefined || command_id == this.command_id) {
-      this.conn.send(msg);
+    if (this.command_id != null &&
+        command_id != null &&
+        this.command_id != command_id) {
+      return;
+    }
+    this.conn.send(msg);
+    if (command_id != null) {
       this.command_id = null;
     }
   },
 
   /**
    * Send a value to client
    *
    * @param object value
@@ -501,17 +507,17 @@ MarionetteDriverActor.prototype = {
     if (this.context == "content") {
       this.sendAsync("executeScript", {value: aRequest.value,
                                        args: aRequest.args,
                                        newSandbox:aRequest.newSandbox});
       return;
     }
 
     let curWindow = this.getCurrentWindow();
-    let marionette = new Marionette(false, curWindow, "chrome", this.marionetteLog);
+    let marionette = new Marionette(this, curWindow, "chrome", this.marionetteLog);
     let _chromeSandbox = this.createExecuteSandbox(curWindow, marionette, aRequest.args);
     if (!_chromeSandbox)
       return;
 
     try {
       _chromeSandbox.finish = function chromeSandbox_finish() {
         return marionette.generate_results();
       };
@@ -608,17 +614,17 @@ MarionetteDriverActor.prototype = {
                                             id: this.command_id,
                                             newSandbox: aRequest.newSandbox});
       return;
     }
 
     let curWindow = this.getCurrentWindow();
     let original_onerror = curWindow.onerror;
     let that = this;
-    let marionette = new Marionette(true, curWindow, "chrome", this.marionetteLog);
+    let marionette = new Marionette(this, curWindow, "chrome", this.marionetteLog);
     marionette.command_id = this.command_id;
 
     function chromeAsyncReturnFunc(value, status) {
       if (value == undefined)
         value = null;
       if (that.command_id == marionette.command_id) {
         if (that.timer != null) {
           that.timer.cancel();
@@ -1176,19 +1182,51 @@ MarionetteDriverActor.prototype = {
     this.sendOk();
     this.messageManager.removeMessageListener("Marionette:ok", this);
     this.messageManager.removeMessageListener("Marionette:done", this);
     this.messageManager.removeMessageListener("Marionette:error", this);
     this.messageManager.removeMessageListener("Marionette:log", this);
     this.messageManager.removeMessageListener("Marionette:testLog", this);
     this.messageManager.removeMessageListener("Marionette:register", this);
     this.messageManager.removeMessageListener("Marionette:goUrl", this);
+    this.messageManager.removeMessageListener("Marionette:runEmulatorCmd", this);
     this.curBrowser = null;
   },
 
+  _emu_cb_id: 0,
+  _emu_cbs: null,
+  runEmulatorCmd: function runEmulatorCmd(cmd, callback) {
+    if (typeof callback != "function") {
+      throw "Need to provide callback function!";
+    }
+    if (!this._emu_cbs) {
+      this._emu_cbs = {};
+    }
+    this._emu_cbs[this._emu_cb_id] = callback;
+    this.sendToClient({emulator_cmd: cmd, id: this._emu_cb_id});
+    this._emu_cb_id += 1;
+  },
+
+  emulatorCmdResult: function emulatorCmdResult(message) {
+    if (this.context != "chrome") {
+      this.sendAsync("emulatorCmdResult", message);
+      return;
+    }
+
+    let cb = this._emu_cbs[message.id];
+    delete this._emu_cbs[message.id];
+    try {
+      cb(message.result);
+    }
+    catch(e) {
+      this.sendError(e.message, e.num, e.stack);
+      return;
+    }
+  },
+
   /**
    * Receives all messages from content messageManager
    */
   receiveMessage: function MDA_receiveMessage(message) {
     switch (message.name) {
       case "DOMContentLoaded":
         this.sendOk();
         this.messageManager.removeMessageListener("DOMContentLoaded", this, true);
@@ -1205,16 +1243,19 @@ MarionetteDriverActor.prototype = {
       case "Marionette:log":
         //log server-side messages
         logger.info(message.json.message);
         break;
       case "Marionette:testLog":
         //log messages from tests
         this.marionetteLog.addLogs(message.json.value);
         break;
+      case "Marionette:runEmulatorCmd":
+        this.sendToClient(message.json);
+        break;
       case "Marionette:register":
         // This code processes the content listener's registration information
         // and either accepts the listener, or ignores it
         let nullPrevious = (this.curBrowser.curFrameId == null);
         let curWin = this.getCurrentWindow();
         let frameObject = curWin.QueryInterface(Components.interfaces.nsIInterfaceRequestor).getInterface(Components.interfaces.nsIDOMWindowUtils).getOuterWindowWithId(message.json.value);
         let reg = this.curBrowser.register(message.json.value, message.json.href);
         if (reg) {
@@ -1264,17 +1305,18 @@ MarionetteDriverActor.prototype.requestT
   "getUrl": MarionetteDriverActor.prototype.getUrl,
   "goBack": MarionetteDriverActor.prototype.goBack,
   "goForward": MarionetteDriverActor.prototype.goForward,
   "refresh":  MarionetteDriverActor.prototype.refresh,
   "getWindow":  MarionetteDriverActor.prototype.getWindow,
   "getWindows":  MarionetteDriverActor.prototype.getWindows,
   "switchToFrame": MarionetteDriverActor.prototype.switchToFrame,
   "switchToWindow": MarionetteDriverActor.prototype.switchToWindow,
-  "deleteSession": MarionetteDriverActor.prototype.deleteSession
+  "deleteSession": MarionetteDriverActor.prototype.deleteSession,
+  "emulatorCmdResult": MarionetteDriverActor.prototype.emulatorCmdResult
 };
 
 /**
  * Creates a BrowserObj. BrowserObjs handle interactions with the
  * browser, according to the current environment (desktop, b2g, etc.)
  *
  * @param nsIDOMWindow win
  *        The window whose browser needs to be accessed
--- a/testing/marionette/marionette-listener.js
+++ b/testing/marionette/marionette-listener.js
@@ -96,16 +96,17 @@ function startListeners() {
   addMessageListenerId("Marionette:isElementDisplayed", isElementDisplayed);
   addMessageListenerId("Marionette:isElementEnabled", isElementEnabled);
   addMessageListenerId("Marionette:isElementSelected", isElementSelected);
   addMessageListenerId("Marionette:sendKeysToElement", sendKeysToElement);
   addMessageListenerId("Marionette:clearElement", clearElement);
   addMessageListenerId("Marionette:switchToFrame", switchToFrame);
   addMessageListenerId("Marionette:deleteSession", deleteSession);
   addMessageListenerId("Marionette:sleepSession", sleepSession);
+  addMessageListenerId("Marionette:emulatorCmdResult", emulatorCmdResult);
 }
 
 /**
  * Called when we start a new session. It registers the
  * current environment, and resets all values
  */
 function newSession(msg) {
   isB2G = msg.json.B2G;
@@ -153,16 +154,17 @@ function deleteSession(msg) {
   removeMessageListenerId("Marionette:isElementDisplayed", isElementDisplayed);
   removeMessageListenerId("Marionette:isElementEnabled", isElementEnabled);
   removeMessageListenerId("Marionette:isElementSelected", isElementSelected);
   removeMessageListenerId("Marionette:sendKeysToElement", sendKeysToElement);
   removeMessageListenerId("Marionette:clearElement", clearElement);
   removeMessageListenerId("Marionette:switchToFrame", switchToFrame);
   removeMessageListenerId("Marionette:deleteSession", deleteSession);
   removeMessageListenerId("Marionette:sleepSession", sleepSession);
+  removeMessageListenerId("Marionette:emulatorCmdResult", emulatorCmdResult);
   this.elementManager.reset();
 }
 
 /*
  * Helper methods 
  */
 
 /**
@@ -232,17 +234,17 @@ function createExecuteContentSandbox(aWi
   let sandbox = new Cu.Sandbox(aWindow);
   sandbox.global = sandbox;
   sandbox.window = aWindow;
   sandbox.document = sandbox.window.document;
   sandbox.navigator = sandbox.window.navigator;
   sandbox.__proto__ = sandbox.window;
   sandbox.testUtils = utils;
 
-  let marionette = new Marionette(false, aWindow, "content", marionetteLogObj);
+  let marionette = new Marionette(this, aWindow, "content", marionetteLogObj);
   sandbox.marionette = marionette;
   marionette.exports.forEach(function(fn) {
     sandbox[fn] = marionette[fn].bind(marionette);
   });
 
   sandbox.SpecialPowers = new SpecialPowers(aWindow);
 
   sandbox.asyncComplete = function sandbox_asyncComplete(value, status) {
@@ -683,10 +685,34 @@ function switchToFrame(msg) {
   }
   curWindow = curWindow.frames[foundFrame];
   curWindow.focus();
   sendOk();
 
   sandbox = null;
 }
 
+let _emu_cb_id = 0;
+let _emu_cbs = {};
+function runEmulatorCmd(cmd, callback) {
+  if (typeof callback != "function") {
+    throw "Need to provide callback function!";
+  }
+  _emu_cbs[_emu_cb_id] = callback;
+  sendAsyncMessage("Marionette:runEmulatorCmd", {emulator_cmd: cmd, id: _emu_cb_id});
+  _emu_cb_id += 1;
+}
+
+function emulatorCmdResult(msg) {
+  let message = msg.json;
+  let cb = _emu_cbs[message.id];
+  delete _emu_cbs[message.id];
+  try {
+    cb(message.result);
+  }
+  catch(e) {
+    sendError(e.message, e.num, e.stack);
+    return;
+  }
+}
+
 //call register self when we get loaded
 registerSelf();
--- a/testing/marionette/marionette-simpletest.js
+++ b/testing/marionette/marionette-simpletest.js
@@ -1,26 +1,27 @@
 /* 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;
+function Marionette(scope, window, context, logObj) {
+  this.scope = scope;
   this.window = window;
   this.tests = [];
   this.logObj = logObj;
   this.context = context;
   this.timeout = 0;
 }
 
 Marionette.prototype = {
-  exports: ['ok', 'is', 'isnot', 'log', 'getLogs', 'generate_results', 'waitFor'],
+  exports: ['ok', 'is', 'isnot', 'log', 'getLogs', 'generate_results', 'waitFor',
+            'runEmulatorCmd'],
 
   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) {
@@ -129,10 +130,15 @@ Marionette.prototype = {
       if (Date.now() - timeout > this.timeout) {
         dump("waitFor timeout: " + test.toString() + "\n");
         // the script will timeout here, so no need to raise a separate
         // timeout exception
         return;
       }
       this.window.setTimeout(this.waitFor.bind(this), 100, callback, test, timeout);
   },
+
+  runEmulatorCmd: function runEmulatorCmd(cmd, callback) {
+    this.scope.runEmulatorCmd(cmd, callback);
+  },
+
 };