Bug 1387380 - Stop capabilities negotiation in Marionette. r?whimboo draft
authorAndreas Tolfsen <ato@sny.no>
Fri, 04 Aug 2017 20:04:12 +0100
changeset 641634 37dd5d989bfdd15fe04459fbac4ef41c5f8e9d04
parent 641632 47248637eafa9a38dade8dc3aa6c4736177c8d8d
child 724863 70f4dffd111231e15558a1f5a2660d2d3a0e514d
push id72602
push userbmo:ato@sny.no
push dateMon, 07 Aug 2017 12:25:04 +0000
reviewerswhimboo
bugs1387380
milestone57.0a1
Bug 1387380 - Stop capabilities negotiation in Marionette. r?whimboo The geckodriver HTTPD proxy implements WebDriver conforming capabilities negotation and it is unnecessary to do this in the Marionette WebDriver service. The capabilities matching that Marionette implements is also not as good as the implementation found in geckodriver. The WebDriver:NewSession command will still accept a JSON Object of "configuration" capabilities that carry the pre-matched capabilities from geckodriver. These will be used as configuration options for the session. Type- and bounds checks will still be performed on this input. MozReview-Commit-ID: CROjgGuTXOG
testing/marionette/client/marionette_driver/marionette.py
testing/marionette/driver.js
testing/marionette/harness/marionette_harness/tests/unit/test_accessibility.py
testing/marionette/harness/marionette_harness/tests/unit/test_capabilities.py
testing/marionette/harness/marionette_harness/tests/unit/test_click.py
testing/marionette/harness/marionette_harness/tests/unit/test_navigation.py
testing/marionette/session.js
testing/marionette/test_session.js
--- a/testing/marionette/client/marionette_driver/marionette.py
+++ b/testing/marionette/client/marionette_driver/marionette.py
@@ -1171,26 +1171,31 @@ class Marionette(object):
         Returns an absolute url for files served from Marionette's www directory.
 
         :param relative_url: The url of a static file, relative to Marionette's www directory.
         '''
         return "{0}{1}".format(self.baseurl, relative_url)
 
     @do_process_check
     def start_session(self, capabilities=None, session_id=None, timeout=60):
-        """Create a new Marionette session.
+        """Create a new WebDriver session.
 
         This method must be called before performing any other action.
 
-        :param capabilities: An optional dict of desired or required capabilities.
+        :param capabilities: An optional dictionary of
+            Marionette-recognised capabilities.  It does not
+            accept a WebDriver conforming capabilities dictionary
+            (including alwaysMatch, firstMatch, desiredCapabilities,
+            or requriedCapabilities), and only recognises extension
+            capabilities that are specific to Marionette.
         :param timeout: Timeout in seconds for the server to be ready.
-        :param session_id: unique identifier for the session. If no session id is
-            passed in then one will be generated by the marionette server.
+        :param session_id: Unique identifier for the session. If no
+            session ID is passed in then one will be generated.
 
-        :returns: A dict of the capabilities offered.
+        :returns: A dictionary of the capabilities offered.
 
         """
         self.crashed = 0
 
         if self.instance:
             returncode = self.instance.runner.returncode
             if returncode is not None:
                 # We're managing a binary which has terminated, so start it again.
--- a/testing/marionette/driver.js
+++ b/testing/marionette/driver.js
@@ -654,30 +654,135 @@ GeckoDriver.prototype.listeningPromise =
         this.mm.removeMessageListener(li, cb);
         resolve();
       }
     };
     this.mm.addMessageListener(li, cb);
   });
 };
 
-/** Create a new session. */
+/**
+ * Create a new WebDriver session.
+ *
+ * It is expected that the caller performs the necessary checks on
+ * the requested capabilities to be WebDriver conforming.  The WebDriver
+ * service offered by Marionette does not match or negotiate capabilities
+ * beyond type- and bounds checks.
+ *
+ * <h3>Capabilities</h3>
+ *
+ * <dl>
+ *  <dt><code>pageLoadStrategy</code> (string)
+ *  <dd>The page load strategy to use for the current session.  Must be
+ *   one of "<tt>none</tt>", "<tt>eager</tt>", and "<tt>normal</tt>".
+ *
+ *  <dt><code>acceptInsecureCerts</code> (boolean)
+ *  <dd>Indicates whether untrusted and self-signed TLS certificates
+ *   are implicitly trusted on navigation for the duration of the session.
+ *
+ *  <dt><code>timeouts</code> (Timeouts object)
+ *  <dd>Describes the timeouts imposed on certian session operations.
+ *
+ *  <dt><code>proxy</code> (Proxy object)
+ *  <dd>Defines the proxy configuration.
+ *
+ *  <dt><code>specificationLevel</code> (number)
+ *  <dd>If set to 1, a WebDriver conforming <i>WebDriver::ElementClick</i>
+ *   implementation will be used.
+ *
+ *  <dt><code>moz:accessibilityChecks</code> (boolean)
+ *  <dd>Run a11y checks when clicking elements.
+ * </dl>
+ *
+ * <h4>Timeouts object</h4>
+ *
+ * <dl>
+ *  <dt><code>script</code> (number)
+ *  <dd>Determines when to interrupt a script that is being evaluates.
+ *
+ *  <dt><code>pageLoad</code> (number)
+ *  <dd>Provides the timeout limit used to interrupt navigation of the
+ *   browsing context.
+ *
+ *  <dt><code>implicit</code> (number)
+ *  <dd>Gives the timeout of when to abort when locating an element.
+ * </dl>
+ *
+ * <h4>Proxy object</h4>
+ *
+ * <dl>
+ *  <dt><code>proxyType</code> (string)
+ *  <dd>Indicates the type of proxy configuration.  Must be one
+ *   of "<tt>pac</tt>", "<tt>direct</tt>", "<tt>autodetect</tt>",
+ *   "<tt>system</tt>", or "<tt>manual</tt>".
+ *
+ *  <dt><code>proxyAutoconfigUrl</code> (string)
+ *  <dd>Defines the URL for a proxy auto-config file if
+ *   <code>proxyType</code> is equal to "<tt>pac</tt>".
+ *
+ *  <dt><code>ftpProxy</code> (string)
+ *  <dd>Defines the proxy host for FTP traffic when the
+ *   <code>proxyType</code> is "<tt>manual</tt>".
+ *
+ *  <dt><code>httpProxy</code> (string)
+ *  <dd>Defines the proxy host for HTTP traffic when the
+ *   <code>proxyType</code> is "<tt>manual</tt>".
+ *
+ *  <dt><code>noProxy</code> (string)
+ *  <dd>Lists the adress for which the proxy should be bypassed when
+ *   the <code>proxyType</code> is "<tt>manual</tt>".  Must be a JSON
+ *   List containing any number of any of domains, IPv4 addresses, or IPv6
+ *   addresses.
+ *
+ *  <dt><code>sslProxy</code> (string)
+ *  <dd>Defines the proxy host for encrypted TLS traffic when the
+ *   <code>proxyType</code> is "<tt>manual</tt>".
+ *
+ *  <dt><code>socksProxy</code> (string)
+ *  <dd>Defines the proxy host for a SOCKS proxy traffic when the
+ *   <code>proxyType</code> is "<tt>manual</tt>".
+ *
+ *  <dt><code>socksVersion</code> (string)
+ *  <dd>Defines the SOCKS proxy version when the <code>proxyType</code> is
+ *   "<tt>manual</tt>".  It must be any integer between 0 and 255
+ *   inclusive.
+ * </dl>
+ *
+ * <h3>Example</h3>
+ *
+ * Input:
+ *
+ * <pre><code>
+ *     {"capabilities": {"acceptInsecureCerts": true}}
+ * </code></pre>
+ *
+ * @param {string=} sessionId
+ *     Normally a unique ID is given to a new session, however this can
+ *     be overriden by providing this field.
+ * @param {Object.<string, *>=} capabilities
+ *     JSON Object containing any of the recognised capabilities listed
+ *     above.
+ *
+ * @return {Object}
+ *     Session ID and capabilities offered by the WebDriver service.
+ *
+ * @throws {SessionNotCreatedError}
+ *     If, for whatever reason, a session could not be created.
+ */
 GeckoDriver.prototype.newSession = function* (cmd, resp) {
   if (this.sessionID) {
     throw new SessionNotCreatedError("Maximum number of active sessions");
   }
 
   this.sessionID = cmd.parameters.sessionId || element.generateUUID();
   this.newSessionCommandId = cmd.id;
 
   try {
     this.capabilities = session.Capabilities.fromJSON(
-        cmd.parameters.capabilities, {merge: true});
-    logger.config("Matched capabilities: " +
-        JSON.stringify(this.capabilities));
+        cmd.parameters.capabilities);
   } catch (e) {
     throw new SessionNotCreatedError(e);
   }
 
   if (!this.secureTLS) {
     logger.warn("TLS certificate errors will be ignored for this session");
     let acceptAllCerts = new cert.InsecureSweepingOverride();
     cert.installOverride(acceptAllCerts);
--- a/testing/marionette/harness/marionette_harness/tests/unit/test_accessibility.py
+++ b/testing/marionette/harness/marionette_harness/tests/unit/test_accessibility.py
@@ -98,18 +98,17 @@ class TestAccessibility(MarionetteTestCa
 
     def run_element_test(self, ids, testFn):
         for id in ids:
             element = self.marionette.find_element(By.ID, id)
             testFn(element)
 
     def setup_accessibility(self, enable_a11y_checks=True, navigate=True):
         self.marionette.delete_session()
-        self.marionette.start_session(
-            {"requiredCapabilities": {"moz:accessibilityChecks": enable_a11y_checks}})
+        self.marionette.start_session({"moz:accessibilityChecks": enable_a11y_checks})
         self.assertEqual(
             self.marionette.session_capabilities["moz:accessibilityChecks"],
             enable_a11y_checks)
 
         # Navigate to test_accessibility.html
         if navigate:
             test_accessibility = self.marionette.absolute_url("test_accessibility.html")
             self.marionette.navigate(test_accessibility)
--- a/testing/marionette/harness/marionette_harness/tests/unit/test_capabilities.py
+++ b/testing/marionette/harness/marionette_harness/tests/unit/test_capabilities.py
@@ -66,48 +66,20 @@ class TestCapabilities(MarionetteTestCas
 
         self.assertIn("moz:accessibilityChecks", self.caps)
         self.assertFalse(self.caps["moz:accessibilityChecks"])
         self.assertIn("specificationLevel", self.caps)
         self.assertEqual(self.caps["specificationLevel"], 0)
 
     def test_set_specification_level(self):
         self.marionette.delete_session()
-        self.marionette.start_session({"desiredCapabilities": {"specificationLevel": 2}})
+        self.marionette.start_session({"specificationLevel": 2})
         caps = self.marionette.session_capabilities
         self.assertEqual(2, caps["specificationLevel"])
 
-        self.marionette.delete_session()
-        self.marionette.start_session({"requiredCapabilities": {"specificationLevel": 3}})
-        caps = self.marionette.session_capabilities
-        self.assertEqual(3, caps["specificationLevel"])
-
-    def test_we_can_pass_in_required_capabilities_on_session_start(self):
-        self.marionette.delete_session()
-        capabilities = {"requiredCapabilities": {"browserName": self.appinfo["name"].lower()}}
-        self.marionette.start_session(capabilities)
-        caps = self.marionette.session_capabilities
-        self.assertIn("browserName", caps)
-
-        # Start a new session just to make sure we leave the browser in the
-        # same state it was before it started the test
-        self.marionette.start_session()
-
-    def test_capability_types(self):
-        for value in ["", "invalid", True, 42, []]:
-            print("testing value {}".format(value))
-            with self.assertRaises(SessionNotCreatedException):
-                print("  with desiredCapabilities")
-                self.marionette.delete_session()
-                self.marionette.start_session({"desiredCapabilities": value})
-            with self.assertRaises(SessionNotCreatedException):
-                print("  with requiredCapabilities")
-                self.marionette.delete_session()
-                self.marionette.start_session({"requiredCapabilities": value})
-
     def test_we_get_valid_uuid4_when_creating_a_session(self):
         self.assertNotIn("{", self.marionette.session_id,
                          "Session ID has {{}} in it: {}".format(
                              self.marionette.session_id))
 
 
 class TestCapabilityMatching(MarionetteTestCase):
     allowed = [None, "*"]
@@ -118,150 +90,58 @@ class TestCapabilityMatching(MarionetteT
         self.browser_name = self.marionette.session_capabilities["browserName"]
         self.platform_name = self.marionette.session_capabilities["platformName"]
         self.delete_session()
 
     def delete_session(self):
         if self.marionette.session is not None:
             self.marionette.delete_session()
 
-    def test_browser_name_desired(self):
-        self.marionette.start_session({"desiredCapabilities": {"browserName": self.browser_name}})
-        self.assertEqual(self.marionette.session_capabilities["browserName"], self.browser_name)
-
-    def test_browser_name_required(self):
-        self.marionette.start_session({"requiredCapabilities": {"browserName": self.browser_name}})
-        self.assertEqual(self.marionette.session_capabilities["browserName"], self.browser_name)
-
-    def test_browser_name_desired_allowed_types(self):
-        for typ in self.allowed:
-            self.delete_session()
-            self.marionette.start_session({"desiredCapabilities": {"browserName": typ}})
-            self.assertEqual(self.marionette.session_capabilities["browserName"], self.browser_name)
-
-    def test_browser_name_desired_disallowed_types(self):
-        for typ in self.disallowed:
-            with self.assertRaises(SessionNotCreatedException):
-                self.marionette.start_session({"desiredCapabilities": {"browserName": typ}})
-
-    def test_browser_name_required_allowed_types(self):
-        for typ in self.allowed:
-            self.delete_session()
-            self.marionette.start_session({"requiredCapabilities": {"browserName": typ}})
-            self.assertEqual(self.marionette.session_capabilities["browserName"], self.browser_name)
-
-    def test_browser_name_requried_disallowed_types(self):
-        for typ in self.disallowed:
+    def test_accept_insecure_certs(self):
+        for value in ["", 42, {}, []]:
+            print("  type {}".format(type(value)))
             with self.assertRaises(SessionNotCreatedException):
-                self.marionette.start_session({"requiredCapabilities": {"browserName": typ}})
-
-    def test_browser_name_prefers_required(self):
-        caps = {"desiredCapabilities": {"browserName": "invalid"},
-                    "requiredCapabilities": {"browserName": "*"}}
-        self.marionette.start_session(caps)
-
-    def test_browser_name_error_on_invalid_required(self):
-        with self.assertRaises(SessionNotCreatedException):
-            caps = {"desiredCapabilities": {"browserName": "*"},
-                        "requiredCapabilities": {"browserName": "invalid"}}
-            self.marionette.start_session(caps)
-
-    # TODO(ato): browser version comparison not implemented yet
-
-    def test_platform_name_desired(self):
-        self.marionette.start_session({"desiredCapabilities": {"platformName": self.platform_name}})
-        self.assertEqual(self.marionette.session_capabilities["platformName"], self.platform_name)
-
-    def test_platform_name_required(self):
-        self.marionette.start_session({"requiredCapabilities": {"platformName": self.platform_name}})
-        self.assertEqual(self.marionette.session_capabilities["platformName"], self.platform_name)
-
-    def test_platform_name_desired_allowed_types(self):
-        for typ in self.allowed:
-            self.delete_session()
-            self.marionette.start_session({"desiredCapabilities": {"platformName": typ}})
-            self.assertEqual(self.marionette.session_capabilities["platformName"], self.platform_name)
-
-    def test_platform_name_desired_disallowed_types(self):
-        for typ in self.disallowed:
-            with self.assertRaises(SessionNotCreatedException):
-                self.marionette.start_session({"desiredCapabilities": {"platformName": typ}})
-
-    def test_platform_name_required_allowed_types(self):
-        for typ in self.allowed:
-            self.delete_session()
-            self.marionette.start_session({"requiredCapabilities": {"platformName": typ}})
-            self.assertEqual(self.marionette.session_capabilities["platformName"], self.platform_name)
-
-    def test_platform_name_requried_disallowed_types(self):
-        for typ in self.disallowed:
-            with self.assertRaises(SessionNotCreatedException):
-                self.marionette.start_session({"requiredCapabilities": {"platformName": typ}})
-
-    def test_platform_name_prefers_required(self):
-        caps = {"desiredCapabilities": {"platformName": "invalid"},
-                    "requiredCapabilities": {"platformName": "*"}}
-        self.marionette.start_session(caps)
-
-    def test_platform_name_error_on_invalid_required(self):
-        with self.assertRaises(SessionNotCreatedException):
-            caps = {"desiredCapabilities": {"platformName": "*"},
-                        "requiredCapabilities": {"platformName": "invalid"}}
-            self.marionette.start_session(caps)
-
-    # TODO(ato): platform version comparison not imlpemented yet
-
-    def test_accept_insecure_certs(self):
-        for capability_type in ["desiredCapabilities", "requiredCapabilities"]:
-            print("testing {}".format(capability_type))
-            for value in ["", 42, {}, []]:
-                print("  type {}".format(type(value)))
-                with self.assertRaises(SessionNotCreatedException):
-                    self.marionette.start_session({capability_type: {"acceptInsecureCerts": value}})
+                self.marionette.start_session({"acceptInsecureCerts": value})
 
         self.delete_session()
-        self.marionette.start_session({"desiredCapabilities": {"acceptInsecureCerts": True}})
-        self.assertTrue(self.marionette.session_capabilities["acceptInsecureCerts"])
-        self.delete_session()
-        self.marionette.start_session({"requiredCapabilities": {"acceptInsecureCerts": True}})
-
+        self.marionette.start_session({"acceptInsecureCerts": True})
         self.assertTrue(self.marionette.session_capabilities["acceptInsecureCerts"])
 
     def test_page_load_strategy(self):
         for strategy in ["none", "eager", "normal"]:
             print("valid strategy {}".format(strategy))
             self.delete_session()
-            self.marionette.start_session({"desiredCapabilities": {"pageLoadStrategy": strategy}})
+            self.marionette.start_session({"pageLoadStrategy": strategy})
             self.assertEqual(self.marionette.session_capabilities["pageLoadStrategy"], strategy)
 
         # A null value should be treatend as "normal"
         self.delete_session()
-        self.marionette.start_session({"desiredCapabilities": {"pageLoadStrategy": None}})
+        self.marionette.start_session({"pageLoadStrategy": None})
         self.assertEqual(self.marionette.session_capabilities["pageLoadStrategy"], "normal")
 
         for value in ["", "EAGER", True, 42, {}, []]:
             print("invalid strategy {}".format(value))
             with self.assertRaisesRegexp(SessionNotCreatedException, "InvalidArgumentError"):
-                self.marionette.start_session({"desiredCapabilities": {"pageLoadStrategy": value}})
+                self.marionette.start_session({"pageLoadStrategy": value})
 
     def test_proxy_default(self):
         self.marionette.start_session()
         self.assertNotIn("proxy", self.marionette.session_capabilities)
 
     def test_proxy_desired(self):
-        self.marionette.start_session({"desiredCapabilities": {"proxy": {"proxyType": "manual"}}})
+        self.marionette.start_session({"proxy": {"proxyType": "manual"}})
         self.assertIn("proxy", self.marionette.session_capabilities)
         self.assertEqual(self.marionette.session_capabilities["proxy"]["proxyType"], "manual")
         self.assertEqual(self.marionette.get_pref("network.proxy.type"), 1)
 
     def test_proxy_required(self):
-        self.marionette.start_session({"requiredCapabilities": {"proxy": {"proxyType": "manual"}}})
+        self.marionette.start_session({"proxy": {"proxyType": "manual"}})
         self.assertIn("proxy", self.marionette.session_capabilities)
         self.assertEqual(self.marionette.session_capabilities["proxy"]["proxyType"], "manual")
         self.assertEqual(self.marionette.get_pref("network.proxy.type"), 1)
 
     def test_timeouts(self):
         timeouts = {u"implicit": 123, u"pageLoad": 456, u"script": 789}
-        caps = {"desiredCapabilities": {"timeouts": timeouts}}
+        caps = {"timeouts": timeouts}
         self.marionette.start_session(caps)
         self.assertIn("timeouts", self.marionette.session_capabilities)
         self.assertDictEqual(self.marionette.session_capabilities["timeouts"], timeouts)
         self.assertDictEqual(self.marionette._send_message("getTimeouts"), timeouts)
--- a/testing/marionette/harness/marionette_harness/tests/unit/test_click.py
+++ b/testing/marionette/harness/marionette_harness/tests/unit/test_click.py
@@ -127,18 +127,17 @@ class TestLegacyClick(MarionetteTestCase
 class TestClick(TestLegacyClick):
     """Uses WebDriver specification compatible element interactability
     checks.
     """
 
     def setUp(self):
         TestLegacyClick.setUp(self)
         self.marionette.delete_session()
-        self.marionette.start_session(
-            {"requiredCapabilities": {"specificationLevel": 1}})
+        self.marionette.start_session({"specificationLevel": 1})
 
     def test_click_element_obscured_by_absolute_positioned_element(self):
         self.marionette.navigate(obscured_overlay)
         overlay = self.marionette.find_element(By.ID, "overlay")
         obscured = self.marionette.find_element(By.ID, "obscured")
 
         overlay.click()
         with self.assertRaises(errors.ElementClickInterceptedException):
--- a/testing/marionette/harness/marionette_harness/tests/unit/test_navigation.py
+++ b/testing/marionette/harness/marionette_harness/tests/unit/test_navigation.py
@@ -604,42 +604,39 @@ class TestTLSNavigation(MarionetteTestCa
     secure_tls = {"acceptInsecureCerts": False}
 
     def setUp(self):
         super(TestTLSNavigation, self).setUp()
 
         self.test_page_insecure = self.fixtures.where_is("test.html", on="https")
 
         self.marionette.delete_session()
-        self.capabilities = self.marionette.start_session(
-            {"requiredCapabilities": self.insecure_tls})
+        self.capabilities = self.marionette.start_session(self.insecure_tls)
 
     def tearDown(self):
         try:
             self.marionette.delete_session()
         except:
             pass
 
         super(TestTLSNavigation, self).tearDown()
 
     @contextlib.contextmanager
     def safe_session(self):
         try:
-            self.capabilities = self.marionette.start_session(
-                {"requiredCapabilities": self.secure_tls})
+            self.capabilities = self.marionette.start_session(self.secure_tls)
             self.assertFalse(self.capabilities["acceptInsecureCerts"])
             yield self.marionette
         finally:
             self.marionette.delete_session()
 
     @contextlib.contextmanager
     def unsafe_session(self):
         try:
-            self.capabilities = self.marionette.start_session(
-                {"requiredCapabilities": self.insecure_tls})
+            self.capabilities = self.marionette.start_session(self.insecure_tls)
             self.assertTrue(self.capabilities["acceptInsecureCerts"])
             yield self.marionette
         finally:
             self.marionette.delete_session()
 
     def test_navigate_by_command(self):
         self.marionette.navigate(self.test_page_insecure)
         self.assertIn("https", self.marionette.get_url())
@@ -674,45 +671,45 @@ class TestPageLoadStrategy(BaseNavigatio
     def tearDown(self):
         self.marionette.delete_session()
         self.marionette.start_session()
 
         super(TestPageLoadStrategy, self).tearDown()
 
     def test_none(self):
         self.marionette.delete_session()
-        self.marionette.start_session({"desiredCapabilities": {"pageLoadStrategy": "none"}})
+        self.marionette.start_session({"pageLoadStrategy": "none"})
 
         # With a strategy of "none" there should be no wait for the page load, and the
         # current load state is unknown. So only test that the command executes successfully.
         self.marionette.navigate(self.test_page_slow_resource)
 
     @skip_if_mobile("Disabling due to message passing slowness on Android.")
     def test_eager(self):
         self.marionette.delete_session()
-        self.marionette.start_session({"desiredCapabilities": {"pageLoadStrategy": "eager"}})
+        self.marionette.start_session({"pageLoadStrategy": "eager"})
 
         self.marionette.navigate(self.test_page_slow_resource)
         self.assertEqual("interactive", self.ready_state)
         self.assertEqual(self.test_page_slow_resource, self.marionette.get_url())
         self.marionette.find_element(By.ID, "slow")
 
     def test_normal(self):
         self.marionette.delete_session()
-        self.marionette.start_session({"desiredCapabilities": {"pageLoadStrategy": "normal"}})
+        self.marionette.start_session({"pageLoadStrategy": "normal"})
 
         self.marionette.navigate(self.test_page_slow_resource)
         self.assertEqual(self.test_page_slow_resource, self.marionette.get_url())
         self.assertEqual("complete", self.ready_state)
         self.marionette.find_element(By.ID, "slow")
 
     @run_if_e10s("Requires e10s mode enabled")
     def test_strategy_after_remoteness_change(self):
         """Bug 1378191 - Reset of capabilities after listener reload"""
         self.marionette.delete_session()
-        self.marionette.start_session({"desiredCapabilities": {"pageLoadStrategy": "eager"}})
+        self.marionette.start_session({"pageLoadStrategy": "eager"})
 
         # Trigger a remoteness change which will reload the listener script
         self.assertTrue(self.is_remote_tab, "Initial tab doesn't have remoteness flag set")
         self.marionette.navigate("about:robots")
         self.assertFalse(self.is_remote_tab, "Tab has remoteness flag set")
         self.marionette.navigate(self.test_page_slow_resource)
         self.assertEqual("interactive", self.ready_state)
--- a/testing/marionette/session.js
+++ b/testing/marionette/session.js
@@ -268,17 +268,17 @@ session.Capabilities = class extends Map
       ["moz:profile", maybeProfile()],
       ["moz:accessibilityChecks", false],
       ["moz:headless", Cc["@mozilla.org/gfx/info;1"].getService(Ci.nsIGfxInfo).isHeadless],
     ]);
   }
 
   /**
    * @param {string} key
-   *     Capability name.
+   *     Capability key.
    * @param {(string|number|boolean)} value
    *     JSON-safe capability value.
    */
   set(key, value) {
     if (key === "timeouts" && !(value instanceof session.Timeouts)) {
       throw new TypeError();
     } else if (key === "proxy" && !(value instanceof session.Proxy)) {
       throw new TypeError();
@@ -297,111 +297,37 @@ session.Capabilities = class extends Map
    */
   toJSON() {
     return marshal(this);
   }
 
   /**
    * Unmarshal a JSON object representation of WebDriver capabilities.
    *
-   * @param {Object.<string, ?>=} json
+   * @param {Object.<string, *>=} json
    *     WebDriver capabilities.
-   * @param {boolean=} merge
-   *     If providing <var>json</var> with <tt>desiredCapabilities</tt> or
-   *     <tt>requiredCapabilities</tt> fields, or both, it should be
-   *     set to true to merge these before parsing.  This indicates that
-   *     the input provided is from a client and not from
-   *     {@link session.Capabilities#toJSON}.
    *
    * @return {session.Capabilities}
    *     Internal representation of WebDriver capabilities.
    */
-  static fromJSON(json, {merge = false} = {}) {
+  static fromJSON(json) {
     if (typeof json == "undefined" || json === null) {
       json = {};
     }
     assert.object(json);
 
-    if (merge) {
-      json = session.Capabilities.merge_(json);
-    }
     return session.Capabilities.match_(json);
   }
 
-  // Processes capabilities as described by WebDriver.
-  static merge_(json) {
-    for (let entry of [json.desiredCapabilities, json.requiredCapabilities]) {
-      if (typeof entry == "undefined" || entry === null) {
-        continue;
-      }
-      assert.object(entry,
-          error.pprint`Expected ${entry} to be a capabilities object`);
-    }
-
-    let desired = json.desiredCapabilities || {};
-    let required = json.requiredCapabilities || {};
-
-    // One level deep union merge of desired- and required capabilities
-    // with preference on required
-    return Object.assign({}, desired, required);
-  }
-
   // Matches capabilities as described by WebDriver.
-  static match_(caps = {}) {
+  static match_(json = {}) {
     let matched = new session.Capabilities();
 
-    const defined = v => typeof v != "undefined" && v !== null;
-    const wildcard = v => v === "*";
-
-    // Iff |actual| provides some value, or is a wildcard or an exact
-    // match of |expected|.  This means it can be null or undefined,
-    // or "*", or "firefox".
-    function stringMatch(actual, expected) {
-      return !defined(actual) || (wildcard(actual) || actual === expected);
-    }
-
-    for (let [k, v] of Object.entries(caps)) {
+    for (let [k, v] of Object.entries(json)) {
       switch (k) {
-        case "browserName":
-          let bname = matched.get("browserName");
-          if (!stringMatch(v, bname)) {
-            throw new TypeError(
-                pprint`Given browserName ${v}, but my name is ${bname}`);
-          }
-          break;
-
-        // TODO(ato): bug 1326397
-        case "browserVersion":
-          let bversion = matched.get("browserVersion");
-          if (!stringMatch(v, bversion)) {
-            throw new TypeError(
-                pprint`Given browserVersion ${v}, ` +
-                pprint`but current version is ${bversion}`);
-          }
-          break;
-
-        case "platformName":
-          let pname = matched.get("platformName");
-          if (!stringMatch(v, pname)) {
-            throw new TypeError(
-                pprint`Given platformName ${v}, ` +
-                pprint`but current platform is ${pname}`);
-          }
-          break;
-
-        // TODO(ato): bug 1326397
-        case "platformVersion":
-          let pversion = matched.get("platformVersion");
-          if (!stringMatch(v, pversion)) {
-            throw new TypeError(
-                pprint`Given platformVersion ${v}, ` +
-                pprint`but current platform version is ${pversion}`);
-          }
-          break;
-
         case "acceptInsecureCerts":
           assert.boolean(v);
           matched.set("acceptInsecureCerts", v);
           break;
 
         case "pageLoadStrategy":
           if (v === null) {
             matched.set("pageLoadStrategy", session.PageLoadStrategy.Normal);
--- a/testing/marionette/test_session.js
+++ b/testing/marionette/test_session.js
@@ -278,72 +278,25 @@ add_test(function test_Capabilities_toJS
   run_next_test();
 });
 
 add_test(function test_Capabilities_fromJSON() {
   const {fromJSON} = session.Capabilities;
 
   // plain
   for (let typ of [{}, null, undefined]) {
-    ok(fromJSON(typ, {merge: true}).has("browserName"));
-    ok(fromJSON(typ, {merge: false}).has("browserName"));
+    ok(fromJSON(typ).has("browserName"));
   }
   for (let typ of [true, 42, "foo", []]) {
-    Assert.throws(() =>
-        fromJSON(typ, {merge: true}), InvalidArgumentError);
-    Assert.throws(() =>
-        fromJSON(typ, {merge: false}), InvalidArgumentError);
-  }
-
-  // merging
-  let desired = {"moz:accessibilityChecks": false};
-  let required = {"moz:accessibilityChecks": true};
-  let matched = fromJSON(
-      {desiredCapabilities: desired, requiredCapabilities: required},
-      {merge: true});
-  ok(matched.has("moz:accessibilityChecks"));
-  equal(true, matched.get("moz:accessibilityChecks"));
-
-  // desiredCapabilities/requriedCapabilities types
-  for (let typ of [undefined, null, {}]) {
-    ok(fromJSON({desiredCapabilities: typ}, {merge: true}));
-    ok(fromJSON({requiredCapabilities: typ}, {merge: true}));
-  }
-  for (let typ of [true, 42, "foo", []]) {
-    Assert.throws(() => fromJSON({desiredCapabilities: typ}, {merge: true}));
-    Assert.throws(() => fromJSON({requiredCapabilities: typ}, {merge: true}));
+    Assert.throws(() => fromJSON(typ), InvalidArgumentError);
   }
 
   // matching
   let caps = new session.Capabilities();
 
-  ok(fromJSON({browserName: caps.get("browserName")}));
-  ok(fromJSON({browserName: null}));
-  ok(fromJSON({browserName: undefined}));
-  ok(fromJSON({browserName: "*"}));
-  Assert.throws(() => fromJSON({browserName: "foo"}));
-
-  ok(fromJSON({browserVersion: caps.get("browserVersion")}));
-  ok(fromJSON({browserVersion: null}));
-  ok(fromJSON({browserVersion: undefined}));
-  ok(fromJSON({browserVersion: "*"}));
-  Assert.throws(() => fromJSON({browserVersion: "foo"}));
-
-  ok(fromJSON({platformName: caps.get("platformName")}));
-  ok(fromJSON({platformName: null}));
-  ok(fromJSON({platformName: undefined}));
-  ok(fromJSON({platformName: "*"}));
-  Assert.throws(() => fromJSON({platformName: "foo"}));
-
-  ok(fromJSON({platformVersion: caps.get("platformVersion")}));
-  ok(fromJSON({platformVersion: null}));
-  ok(fromJSON({platformVersion: undefined}));
-  ok(fromJSON({platformVersion: "*"}));
-  Assert.throws(() => fromJSON({platformVersion: "foo"}));
-
   caps = fromJSON({acceptInsecureCerts: true});
   equal(true, caps.get("acceptInsecureCerts"));
   caps = fromJSON({acceptInsecureCerts: false});
   equal(false, caps.get("acceptInsecureCerts"));
   Assert.throws(() => fromJSON({acceptInsecureCerts: "foo"}));
 
   for (let strategy of Object.values(session.PageLoadStrategy)) {
     caps = fromJSON({pageLoadStrategy: strategy});