Bug 940554 - Fix Marionette's newSession to return capabilities. r=mdas
☠☠ backed out by 3b88bcc4c3d4 ☠ ☠
authorAndreas Tolfsen <ato@mozilla.com>
Tue, 28 Jan 2014 17:54:44 -0500
changeset 181672 e5dfc4abbb9103b2ae1ba4a2e957994abfdbe9ba
parent 181671 6df2988ebe202eb700ee9442c49c70d7ecd15597
child 181673 0fbc3cd1bf548f4796bcefb5726c6565cde31dbe
push id3343
push userffxbld
push dateMon, 17 Mar 2014 21:55:32 +0000
treeherdermozilla-beta@2f7d3415f79f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmdas
bugs940554
milestone29.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 940554 - Fix Marionette's newSession to return capabilities. r=mdas
testing/marionette/client/marionette/marionette.py
testing/marionette/client/marionette/tests/unit/test_session.py
testing/marionette/client/marionette/tests/unit/unit-tests.ini
testing/marionette/marionette-server.js
--- a/testing/marionette/client/marionette/marionette.py
+++ b/testing/marionette/client/marionette/marionette.py
@@ -571,49 +571,50 @@ class Marionette(object):
                 if '"from"' in data:
                     time.sleep(5)
                     return True
             except socket.error:
                 pass
             time.sleep(1)
         return False
 
-    def _send_message(self, command, response_key, **kwargs):
-        if not self.session and command not in ('newSession', 'getStatus'):
-            raise MarionetteException(message="Please start a session")
+    def _send_message(self, command, response_key="ok", **kwargs):
+        if not self.session and command not in ("newSession", "getStatus"):
+            raise MarionetteException("Please start a session")
 
-        message = { 'name': command }
+        message = {"name": command}
         if self.session:
-            message['sessionId'] = self.session
+            message["sessionId"] = self.session
         if kwargs:
-            message['parameters'] = kwargs
+            message["parameters"] = kwargs
 
         try:
             response = self.client.send(message)
-        except socket.timeout:
+        except socket.timeout as e:
             self.session = None
             self.window = None
             self.client.close()
-            raise TimeoutException(message='socket.timeout', status=ErrorCodes.TIMEOUT, stacktrace=None)
+            raise TimeoutException(
+                "Connection timed out", status=ErrorCodes.TIMEOUT,
+                cause=e)
 
         # 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:
+        if response_key in response:
             return response[response_key]
-        else:
-            self._handle_error(response)
+        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.")
+            raise MarionetteException(
+                "No emulator in this test to run command against")
         cmd = cmd.encode("ascii")
         result = self.emulator._run_telnet(cmd)
         return self.client.send({"name": "emulatorCmdResult",
                                  "id": response.get("id"),
                                  "result": result})
 
     def _handle_error(self, response):
         if 'error' in response and isinstance(response['error'], dict):
@@ -694,21 +695,27 @@ class Marionette(object):
         :param relative_url: The url of a static file, relative to Marionette's www directory.
         '''
         return "%s%s" % (self.baseurl, relative_url)
 
     def status(self):
         return self._send_message('getStatus', 'value')
 
     def start_session(self, desired_capabilities=None):
-        '''
-        Creates a new Marionette session.
+        """Create a new Marionette session.
+
+        This method must be called before performing any other action.
 
-        You must call this method before performing any other action.
-        '''
+        :params desired_capabilities: An optional dict of desired
+            capabilities.  This is currently ignored.
+
+        :returns: A dict of the capabilities offered.
+
+        """
+
         try:
             # We are ignoring desired_capabilities, at least for now.
             self.session = self._send_message('newSession', 'value')
         except:
             exc, val, tb = sys.exc_info()
             self.check_for_crash()
             raise exc, val, tb
 
new file mode 100644
--- /dev/null
+++ b/testing/marionette/client/marionette/tests/unit/test_session.py
@@ -0,0 +1,32 @@
+# 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/.
+
+import marionette_test
+
+class TestSession(marionette_test.MarionetteTestCase):
+    def setUp(self):
+        super(TestSession, self).setUp()
+        self.marionette.delete_session()
+
+    def test_new_session_returns_capabilities(self):
+        # Sends newSession
+        caps = self.marionette.start_session()
+
+        # Check that session was created.  This implies the server
+        # sent us the sessionId and status fields.
+        self.assertIsNotNone(self.marionette.session)
+
+        # Required capabilities mandated by WebDriver spec
+        self.assertIn("browserName", caps)
+        self.assertIn("platformName", caps)
+        self.assertIn("platformVersion", caps)
+
+        # Optional capabilities we want Marionette to support
+        self.assertIn("cssSelectorsEnabled", caps)
+        self.assertIn("device", caps)
+        self.assertIn("handlesAlerts", caps)
+        self.assertIn("javascriptEnabled", caps)
+        self.assertIn("rotatable", caps)
+        self.assertIn("takesScreenshot", caps)
+        self.assertIn("version", caps)
--- a/testing/marionette/client/marionette/tests/unit/unit-tests.ini
+++ b/testing/marionette/client/marionette/tests/unit/unit-tests.ini
@@ -6,16 +6,18 @@ qemu = false
 browser = true
 
 ; true if the test is compatible with b2g, otherwise false
 b2g = true
 
 ; true if the test should be skipped
 skip = false
 
+[test_session.py]
+
 [test_expectedfail.py]
 expected = fail
 [test_getstatus.py]
 [test_import_script.py]
 [test_import_script_reuse_window.py]
 b2g = false
 [test_click.py]
 [test_click_chrome.py]
--- a/testing/marionette/marionette-server.js
+++ b/testing/marionette/marionette-server.js
@@ -484,78 +484,83 @@ MarionetteServerConnection.prototype = {
    * the frame scripts and with responses sent to the client.  This prevents
    * commands and responses from getting out-of-sync, which can happen in
    * the case of execute_async calls that timeout and then later send a
    * response, and other situations.  See bug 779011. See setScriptTimeout()
    * for a basic example.
    */
 
   /**
-   * Create a new session. This creates a BrowserObj.
+   * Create a new session. This creates a new BrowserObj.
    *
-   * In a desktop environment, this opens a new 'about:blank' tab for
-   * the client to test in.
+   * In a desktop environment, this opens a new browser with
+   * "about:blank" which subsequent commands will be sent to.
    *
+   * This will send a hash map of supported capabilities to the client
+   * as part of the Marionette:register IPC command in the
+   * receiveMessage callback when a new browser is created.
    */
   newSession: function MDA_newSession() {
     this.command_id = this.getCommandId();
     this.newSessionCommandId = this.command_id;
 
     this.scriptTimeout = 10000;
 
     function waitForWindow() {
       let checkTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
       let win = this.getCurrentWindow();
       if (!win ||
           (appName == "Firefox" && !win.gBrowser) ||
           (appName == "Fennec" && !win.BrowserApp)) {
-        checkTimer.initWithCallback(waitForWindow.bind(this), 100, Ci.nsITimer.TYPE_ONE_SHOT);
+        checkTimer.initWithCallback(waitForWindow.bind(this), 100,
+                                    Ci.nsITimer.TYPE_ONE_SHOT);
       }
       else {
         this.startBrowser(win, true);
       }
     }
 
-
     if (!Services.prefs.getBoolPref("marionette.contentListener")) {
       waitForWindow.call(this);
     }
     else if ((appName != "Firefox") && (this.curBrowser == null)) {
-      //if there is a content listener, then we just wake it up
+      // If there is a content listener, then we just wake it up
       this.addBrowser(this.getCurrentWindow());
-      this.curBrowser.startSession(false, this.getCurrentWindow(), this.whenBrowserStarted);
+      this.curBrowser.startSession(false, this.getCurrentWindow(),
+                                   this.whenBrowserStarted);
       this.messageManager.broadcastAsyncMessage("Marionette:restart", {});
     }
     else {
-      this.sendError("Session already running", 500, null, this.command_id);
+      this.sendError("Session already running", 500, null,
+                     this.command_id);
     }
     this.switchToGlobalMessageManager();
   },
 
   getSessionCapabilities: function MDA_getSessionCapabilities(){
     this.command_id = this.getCommandId();
 
     let rotatable = appName == "B2G" ? true : false;
 
     let value = {
-          'appBuildId' : Services.appinfo.appBuildID,
-          'XULappId' : Services.appinfo.ID,
-          'cssSelectorsEnabled': true,
-          'browserName': appName,
-          'handlesAlerts': false,
-          'javascriptEnabled': true,
-          'nativeEvents': false,
-          'platformName': Services.appinfo.OS,
-          'platformVersion': Services.appinfo.platformVersion,
-          'secureSsl': false,
-          'device': qemu == "1" ? "qemu" : (!device ? "desktop" : device),
-          'rotatable': rotatable,
-          'takesScreenshot': true,
-          'takesElementScreenshot': true,
-          'version': Services.appinfo.version
+      'appBuildId' : Services.appinfo.appBuildID,
+      'XULappId' : Services.appinfo.ID,
+      'cssSelectorsEnabled': true,
+      'browserName': appName,
+      'handlesAlerts': false,
+      'javascriptEnabled': true,
+      'nativeEvents': false,
+      'platformName': Services.appinfo.OS,
+      'platformVersion': Services.appinfo.platformVersion,
+      'secureSsl': false,
+      'device': qemu == "1" ? "qemu" : (!device ? "desktop" : device),
+      'rotatable': rotatable,
+      'takesScreenshot': true,
+      'takesElementScreenshot': true,
+      'version': Services.appinfo.version
     };
 
     this.sendResponse(value, this.command_id);
   },
 
   getStatus: function MDA_getStatus(){
     this.command_id = this.getCommandId();
 
@@ -2382,17 +2387,17 @@ MarionetteServerConnection.prototype = {
         this.curBrowser.elementManager.seenItems[reg.id] = Cu.getWeakReference(listenerWindow);
         if (nullPrevious && (this.curBrowser.curFrameId != null)) {
           if (!this.sendAsync("newSession",
                               { B2G: (appName == "B2G") },
                               this.newSessionCommandId)) {
             return;
           }
           if (this.curBrowser.newSession) {
-            this.sendResponse(reg.id, this.newSessionCommandId);
+            this.getSessionCapabilities();
             this.newSessionCommandId = null;
           }
         }
         return reg;
     }
   }
 };