Bug 759485: Add submit to HTMLElement on Marionette to submit forms without click() or send_keys(); r=mdas
authorDavid Burns <dburns@mozilla.com>
Tue, 15 Oct 2013 15:40:48 +0100
changeset 164617 cba664ebefdc03ce1275589859c2ec90f6c599ce
parent 164616 979ff52f148272dc31ddcb2ffce964b2ea475422
child 164618 01cb360b412b7a5fbf72e8099049ede32c8ee432
push id3066
push userakeybl@mozilla.com
push dateMon, 09 Dec 2013 19:58:46 +0000
treeherdermozilla-beta@a31a0dce83aa [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmdas
bugs759485
milestone27.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 759485: Add submit to HTMLElement on Marionette to submit forms without click() or send_keys(); r=mdas
testing/marionette/client/marionette/marionette.py
testing/marionette/client/marionette/tests/unit/test_submit.py
testing/marionette/client/marionette/tests/unit/unit-tests.ini
testing/marionette/client/marionette/www/formPage.html
testing/marionette/marionette-listener.js
testing/marionette/marionette-server.js
--- a/testing/marionette/client/marionette/marionette.py
+++ b/testing/marionette/client/marionette/marionette.py
@@ -155,16 +155,21 @@ class HTMLElement(object):
         '''
         Gets the value of the specified CSS property name.
 
         :param property_name: Property name to get the value of.
         '''
         return self.marionette._send_message('getElementValueOfCssProperty', 'value',
                                              id=self.id,
                                              propertyName=property_name)
+    def submit(self):
+        '''
+        Submits if the element is a form or is within a form
+        '''
+        return self.marionette._send_message('submitElement', 'ok', id=self.id)
 
 class Actions(object):
     '''
     An Action object represents a set of actions that are executed in a particular order.
 
     All action methods (press, etc.) return the Actions object itself, to make
     it easy to create a chain of events.
 
new file mode 100644
--- /dev/null
+++ b/testing/marionette/client/marionette/tests/unit/test_submit.py
@@ -0,0 +1,38 @@
+# 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 time
+from marionette_test import MarionetteTestCase
+from errors import NoSuchElementException
+
+
+class TestSubmit(MarionetteTestCase):
+
+    def test_should_be_able_to_submit_forms(self):
+        test_html = self.marionette.absolute_url("formPage.html")
+        self.marionette.navigate(test_html)
+        self.marionette.find_element("name", "login").submit()
+        self.assertEqual(self.marionette.title, "We Arrive Here")
+
+    def test_should_submit_a_form_when_any_input_element_within_that_form_is_submitted(self):
+        test_html = self.marionette.absolute_url("formPage.html")
+        self.marionette.navigate(test_html)
+        self.marionette.find_element("id", "checky").submit()
+        for i in range(5):
+            try:
+                self.marionette.find_element('id', 'email')
+            except NoSuchElementException:
+                time.sleep(1)
+        self.assertEqual(self.marionette.title, "We Arrive Here")
+
+    def test_should_submit_a_form_when_any_element_wihin_that_form_is_submitted(self):
+        test_html = self.marionette.absolute_url("formPage.html")
+        self.marionette.navigate(test_html)
+        self.marionette.find_element("xpath", "//form/p").submit()
+        for i in range(5):
+            try:
+                self.marionette.find_element('id', 'email')
+            except NoSuchElementException:
+                time.sleep(1)
+        self.assertEqual(self.marionette.title, "We Arrive Here")
--- a/testing/marionette/client/marionette/tests/unit/unit-tests.ini
+++ b/testing/marionette/client/marionette/tests/unit/unit-tests.ini
@@ -88,8 +88,9 @@ b2g = false
 [test_window_title.py]
 b2g = false
 [test_window_type.py]
 b2g = false
 
 [test_implicit_waits.py]
 [test_date_time_value.py]
 [test_getactiveframe_oop.py]
+[test_submit.py]
\ No newline at end of file
--- a/testing/marionette/client/marionette/www/formPage.html
+++ b/testing/marionette/client/marionette/www/formPage.html
@@ -1,16 +1,116 @@
-<html>
-<head>
-    <title>We Leave From Here</title>
-
-    <script type="text/javascript">
-        function changePage() {
-            var newLocation = '/common/page/3';
-            window.location = newLocation;
-        }
-    </script>
-</head>
-<body>
-<input type='button' id='killIframe' onclick='top.remove();' value="Kill containing iframe" />
-
-</body>
-</html>
+<html>
+<head>
+    <title>We Leave From Here</title>
+
+    <script type="text/javascript">
+        function changePage() {
+            var newLocation = '/common/page/3';
+            window.location = newLocation;
+        }
+    </script>
+</head>
+<body>
+There should be a form here:
+
+<form method="get" action="resultPage.html" name="login">
+    <input type="email" id="email"/>
+    <input type="submit" id="submitButton" value="Hello there"/>
+</form>
+
+<form method="get" action="resultPage.html" name="optional" style="display: block">
+    Here's a checkbox:
+    <input type="checkbox" id="checky" name="checky" value="furrfu"/>
+    <input type="checkbox" id="checkedchecky" name="checkedchecky" checked="checked" />
+    <input type="checkbox" id="disabledchecky" disabled="disabled" name="disabledchecky" />
+    <input type="checkbox" id="randomly_disabled_checky" disabled="somerandomstring" checked="checked" name="randomlydisabledchecky" />
+    <br/>
+    <select name="selectomatic">
+        <option selected="selected" id="non_multi_option" value="one">One</option>
+        <option value="two">Two</option>
+        <option value="four">Four</option>
+        <option value="still learning how to count, apparently">Still learning how to count, apparently</option>
+    </select>
+
+    <select name="multi" id="multi" multiple="multiple">
+        <option selected="selected" value="eggs">Eggs</option>
+        <option value="ham">Ham</option>
+        <option selected="selected" value="sausages">Sausages</option>
+        <option value="onion gravy">Onion gravy</option>
+    </select>
+
+    <select name="no-select" disabled="disabled">
+      <option value="foo">Foo</option>
+    </select>
+
+		<select name="select_empty_multiple" multiple>
+			<option id="multi_1" value="select_1">select_1</option>
+			<option id="multi_2" value="select_2">select_2</option>
+			<option id="multi_3" value="select_3">select_3</option>
+			<option id="multi_4" value="select_4">select_4</option>
+		</select>
+
+		<select name="multi_true" multiple="true">
+			<option id="multi_true_1" value="select_1">select_1</option>
+			<option id="multi_true_2" value="select_2">select_2</option>
+		</select>
+
+		<select name="multi_false" multiple="false">
+			<option id="multi_false_1" value="select_1">select_1</option>
+			<option id="multi_false_2" value="select_2">select_2</option>
+		</select>
+
+    <select id="invisi_select" style="opacity:0;">
+      <option selected value="apples">Apples</option>
+      <option value="oranges">Oranges</option>
+    </select>
+
+    <select name="select-default">
+        <option>One</option>
+        <option>Two</option>
+        <option>Four</option>
+        <option>Still learning how to count, apparently</option>
+    </select>
+
+    <select name="select_with_spaces">
+        <option>One</option>
+        <option>  Two  </option>
+        <option>
+          Four
+        </option>
+        <option>
+          Still learning   how to count,
+          apparently
+        </option>
+    </select>
+
+    <select>
+    	<option id="blankOption"></option>
+    	<option id="optionEmptyValueSet" value="">nothing</option>
+    </select>
+
+    <br/>
+
+    <input type="radio" id="cheese" name="snack" value="cheese"/>Cheese<br/>
+    <input type="radio" id="peas" name="snack" value="peas"/>Peas<br/>
+    <input type="radio" id="cheese_and_peas" name="snack" value="cheese and peas" checked/>Cheese and peas<br/>
+    <input type="radio" id="nothing" name="snack" value="nowt" disabled="disabled"/>Not a sausage<br/>
+    <input type="radio" id="randomly_disabled_nothing" name="snack" value="funny nowt" disabled="somedisablingstring"/>Not another sausage
+
+    <input type="hidden" name="hidden" value="fromage" />
+
+    <p id="cheeseLiker">I like cheese</p>
+    <input type="submit" value="Click!"/>
+    
+    <input type="radio" id="lone_disabled_selected_radio" name="not_a_snack" value="cumberland" checked="checked" disabled="disabled" />Cumberland sausage
+</form>
+
+<input type='button' id='killIframe' onclick='top.remove();' value="Kill containing iframe" />
+
+<form method="get" action="formPage.html">
+  <p>
+    <label for="checkbox-with-label" id="label-for-checkbox-with-label">Label</label><input type="checkbox" id="checkbox-with-label" />
+  </p>
+</form>
+<input id="vsearchGadget" name="SearchableText" type="text" size="18" value="" title="Hvad s√łger du?" accesskey="4" class="inputLabel" />
+</body>
+</html>
--- a/testing/marionette/marionette-listener.js
+++ b/testing/marionette/marionette-listener.js
@@ -136,17 +136,18 @@ function startListeners() {
   addMessageListenerId("Marionette:findElementContent", findElementContent);
   addMessageListenerId("Marionette:findElementsContent", findElementsContent);
   addMessageListenerId("Marionette:getActiveElement", getActiveElement);
   addMessageListenerId("Marionette:clickElement", clickElement);
   addMessageListenerId("Marionette:getElementAttribute", getElementAttribute);
   addMessageListenerId("Marionette:getElementText", getElementText);
   addMessageListenerId("Marionette:getElementTagName", getElementTagName);
   addMessageListenerId("Marionette:isElementDisplayed", isElementDisplayed);
-  addMessageListenerId("Marionette:getElementValueOfCssProperty", getElementValueOfCssProperty)
+  addMessageListenerId("Marionette:getElementValueOfCssProperty", getElementValueOfCssProperty);
+  addMessageListenerId("Marionette:submitElement", submitElement);
   addMessageListenerId("Marionette:getElementSize", getElementSize);
   addMessageListenerId("Marionette:isElementEnabled", isElementEnabled);
   addMessageListenerId("Marionette:isElementSelected", isElementSelected);
   addMessageListenerId("Marionette:sendKeysToElement", sendKeysToElement);
   addMessageListenerId("Marionette:getElementPosition", getElementPosition);
   addMessageListenerId("Marionette:clearElement", clearElement);
   addMessageListenerId("Marionette:switchToFrame", switchToFrame);
   addMessageListenerId("Marionette:deleteSession", deleteSession);
@@ -212,16 +213,17 @@ function deleteSession(msg) {
   removeMessageListenerId("Marionette:findElementContent", findElementContent);
   removeMessageListenerId("Marionette:findElementsContent", findElementsContent);
   removeMessageListenerId("Marionette:getActiveElement", getActiveElement);
   removeMessageListenerId("Marionette:clickElement", clickElement);
   removeMessageListenerId("Marionette:getElementAttribute", getElementAttribute);
   removeMessageListenerId("Marionette:getElementTagName", getElementTagName);
   removeMessageListenerId("Marionette:isElementDisplayed", isElementDisplayed);
   removeMessageListenerId("Marionette:getElementValueOfCssProperty", getElementValueOfCssProperty);
+  removeMessageListenerId("Marionette:submitElement", submitElement);
   removeMessageListenerId("Marionette:getElementSize", getElementSize);
   removeMessageListenerId("Marionette:isElementEnabled", isElementEnabled);
   removeMessageListenerId("Marionette:isElementSelected", isElementSelected);
   removeMessageListenerId("Marionette:sendKeysToElement", sendKeysToElement);
   removeMessageListenerId("Marionette:getElementPosition", getElementPosition);
   removeMessageListenerId("Marionette:clearElement", clearElement);
   removeMessageListenerId("Marionette:switchToFrame", switchToFrame);
   removeMessageListenerId("Marionette:deleteSession", deleteSession);
@@ -1404,16 +1406,42 @@ function getElementValueOfCssProperty(ms
                  command_id);
   }
   catch (e) {
     sendError(e.message, e.code, e.stack, command_id);
   }
 }
 
 /**
+  * Submit a form on a content page by either using form or element in a form
+  * @param object msg
+  *               'json' JSON object containing 'id' member of the element
+  */
+function submitElement (msg) {
+  let command_id = msg.json.command_id;
+  try {
+    let el = elementManager.getKnownElement(msg.json.id, curFrame);
+    while (el.parentNode != null && el.tagName.toLowerCase() != 'form') {
+      el = el.parentNode;
+    }
+    if (el.tagName && el.tagName.toLowerCase() == 'form') {
+      el.submit();
+      sendOk(command_id);
+    }
+    else {
+      sendError("Element is not a form element or in a form", 7, null, command_id);
+    }
+
+  }
+  catch (e) {
+    sendError(e.message, e.code, e.stack, command_id);
+  }
+}
+
+/**
  * Get the size of the element and return it
  */
 function getElementSize(msg){
   let command_id = msg.json.command_id;
   try {
     let el = elementManager.getKnownElement(msg.json.id, curFrame);
     let clientRect = el.getBoundingClientRect();
     sendResponse({value: {width: clientRect.width, height: clientRect.height}},
--- a/testing/marionette/marionette-server.js
+++ b/testing/marionette/marionette-server.js
@@ -1692,16 +1692,32 @@ MarionetteServerConnection.prototype = {
   getElementValueOfCssProperty: function MDA_getElementValueOfCssProperty(aRequest){
     let command_id = this.command_id = this.getCommandId();
     this.sendAsync("getElementValueOfCssProperty",
                    {id: aRequest.parameters.id, propertyName: aRequest.parameters.propertyName},
                    command_id);
   },
 
   /**
+   * Submit a form on a content page by either using form or element in a form
+   * @param object aRequest
+   *               'id' member holds the reference id to
+   *               the element that will be checked
+  */
+  submitElement: function MDA_submitElement(aRequest) {
+    let command_id = this.command_id = this.getCommandId();
+    if (this.context == "chrome") {
+      this.sendError("Command 'submitElement' is not available in chrome context", 500, null, this.command_id);
+    }
+    else {
+      this.sendAsync("submitElement", {id: aRequest.parameters.id}, command_id);
+    }
+  },
+
+  /**
    * Check if element is enabled
    *
    * @param object aRequest
    *        'id' member holds the reference id to
    *        the element that will be checked
    */
   isElementEnabled: function MDA_isElementEnabled(aRequest) {
     let command_id = this.command_id = this.getCommandId();
@@ -2232,16 +2248,17 @@ MarionetteServerConnection.prototype.req
   "findElement": MarionetteServerConnection.prototype.findElement,
   "findElements": MarionetteServerConnection.prototype.findElements,
   "clickElement": MarionetteServerConnection.prototype.clickElement,
   "getElementAttribute": MarionetteServerConnection.prototype.getElementAttribute,
   "getElementText": MarionetteServerConnection.prototype.getElementText,
   "getElementTagName": MarionetteServerConnection.prototype.getElementTagName,
   "isElementDisplayed": MarionetteServerConnection.prototype.isElementDisplayed,
   "getElementValueOfCssProperty": MarionetteServerConnection.prototype.getElementValueOfCssProperty,
+  "submitElement": MarionetteServerConnection.prototype.submitElement,
   "getElementSize": MarionetteServerConnection.prototype.getElementSize,
   "isElementEnabled": MarionetteServerConnection.prototype.isElementEnabled,
   "isElementSelected": MarionetteServerConnection.prototype.isElementSelected,
   "sendKeysToElement": MarionetteServerConnection.prototype.sendKeysToElement,
   "getElementPosition": MarionetteServerConnection.prototype.getElementPosition,
   "clearElement": MarionetteServerConnection.prototype.clearElement,
   "getTitle": MarionetteServerConnection.prototype.getTitle,
   "getWindowType": MarionetteServerConnection.prototype.getWindowType,