Bug 1203074: Move caret to end of textual input field before sending keys
authorAndreas Tolfsen <ato@mozilla.com>
Wed, 09 Sep 2015 14:53:53 +0100
changeset 295632 86bd982df1f9b339dd94d3dac25cbba7d5f20a15
parent 295631 b6e914c70e41d8acdd3157582ed8357f9ffdcf22
child 295633 2074a0012421534e32c094dc36edff8c720d31b6
push id5245
push userraliiev@mozilla.com
push dateThu, 29 Oct 2015 11:30:51 +0000
treeherdermozilla-beta@dac831dc1bd0 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
bugs1203074
milestone43.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 1203074: Move caret to end of textual input field before sending keys When a TEXTAREA element is focused it returns the cursor to the last position was at, or places it last. INPUT @type="text" (or any other textual input element) places the caret at the beginning. Because of this we move the caret to the end of the input field. The next time the element is focussed, the cursor should move to the end. The layout touch caret tests relied on the caret being left in its previous position. This patch addresses that by using the advanced user interaction API for these test cases. r=jgriffin
layout/base/tests/marionette/test_touchcaret.py
testing/marionette/client/marionette/tests/unit/test_typing.py
testing/marionette/sendkeys.js
--- a/layout/base/tests/marionette/test_touchcaret.py
+++ b/layout/base/tests/marionette/test_touchcaret.py
@@ -1,18 +1,20 @@
 # -*- coding: utf-8 -*-
 # 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 string
+
+from marionette import MarionetteTestCase
+
 from marionette_driver.by import By
 from marionette_driver.marionette import Actions
 from marionette_driver.selection import SelectionManager
-from marionette import MarionetteTestCase
-import string
 
 
 class CommonCaretTestCase(object):
     '''Common test cases for a collapsed selection with a single caret.
 
     To run these test cases, a subclass must inherit from both this class and
     MarionetteTestCase.
 
@@ -93,17 +95,17 @@ class CommonCaretTestCase(object):
 
         # Tap the front of the input to make touch caret appear.
         el.tap(caret0_x, caret0_y)
 
         # Move touch caret
         self.actions.flick(el, touch_caret0_x, touch_caret0_y,
                            touch_caret1_x, touch_caret1_y).perform()
 
-        el.send_keys(content_to_add)
+        self.actions.key_down(content_to_add).key_up(content_to_add).perform()
         assertFunc(target_content, sel.content)
 
     def _test_move_caret_to_end_by_dragging_touch_caret_to_bottom_right_corner(self, el, assertFunc):
         sel = SelectionManager(el)
         content_to_add = '!'
         target_content = sel.content + content_to_add
 
         # Tap the front of the input to make touch caret appear.
@@ -111,17 +113,17 @@ class CommonCaretTestCase(object):
         sel.move_caret_to_front()
         el.tap(*sel.caret_location())
 
         # Move touch caret to the bottom-right corner of the element.
         src_x, src_y = sel.touch_caret_location()
         dest_x, dest_y = el.size['width'], el.size['height']
         self.actions.flick(el, src_x, src_y, dest_x, dest_y).perform()
 
-        el.send_keys(content_to_add)
+        self.actions.key_down(content_to_add).key_up(content_to_add).perform()
         assertFunc(target_content, sel.content)
 
     def _test_move_caret_to_front_by_dragging_touch_caret_to_front_of_content(self, el, assertFunc):
         sel = SelectionManager(el)
         content_to_add = '!'
         target_content = content_to_add + sel.content
 
         # Get touch caret location at the front.
@@ -136,17 +138,17 @@ class CommonCaretTestCase(object):
         sel.move_caret_to_end()
         sel.move_caret_by_offset(1, backward=True)
         el.tap(*sel.caret_location())
         src_x, src_y = sel.touch_caret_location()
 
         # Move touch caret to the front of the input box.
         self.actions.flick(el, src_x, src_y, dest_x, dest_y).perform()
 
-        el.send_keys(content_to_add)
+        self.actions.key_down(content_to_add).key_up(content_to_add).perform()
         assertFunc(target_content, sel.content)
 
     def _test_touch_caret_timeout_by_dragging_it_to_top_left_corner_after_timout(self, el, assertFunc):
         sel = SelectionManager(el)
         content_to_add = '!'
         non_target_content = content_to_add + sel.content
 
         # Get touch caret expiration time in millisecond, and convert it to second.
@@ -164,17 +166,17 @@ class CommonCaretTestCase(object):
         el.tap(*sel.caret_location())
 
         # Wait until touch caret disappears, then pretend to move it to the
         # top-left corner of the input box.
         src_x, src_y = sel.touch_caret_location()
         dest_x, dest_y = 0, 0
         self.actions.wait(timeout).flick(el, src_x, src_y, dest_x, dest_y).perform()
 
-        el.send_keys(content_to_add)
+        self.actions.key_down(content_to_add).key_up(content_to_add).perform()
         assertFunc(non_target_content, sel.content)
 
     def _test_touch_caret_hides_after_receiving_wheel_event(self, el, assertFunc):
         sel = SelectionManager(el)
         content_to_add = '!'
         non_target_content = content_to_add + sel.content
 
         # Tap to make touch caret appear. Note: it's strange that when the caret
@@ -199,17 +201,17 @@ class CommonCaretTestCase(object):
                                  0, 10, 0, WheelEvent.DOM_DELTA_PIXEL,
                                  0, 0, 0, 0);
             ''',
             script_args=[el_center_x, el_center_y],
             sandbox='system'
         )
         self.actions.flick(el, src_x, src_y, dest_x, dest_y).perform()
 
-        el.send_keys(content_to_add)
+        self.actions.key_down(content_to_add).key_up(content_to_add).perform()
         assertFunc(non_target_content, sel.content)
 
     def _test_caret_not_appear_when_typing_in_scrollable_content(self, el, assertFunc):
         sel = SelectionManager(el)
 
         content_to_add = '!'
         target_content = sel.content + string.ascii_letters + content_to_add
 
--- a/testing/marionette/client/marionette/tests/unit/test_typing.py
+++ b/testing/marionette/client/marionette/tests/unit/test_typing.py
@@ -1,19 +1,24 @@
 # 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 urllib
+
 from marionette.marionette_test import MarionetteTestCase, skip_if_b2g
 from marionette_driver.keys import Keys
 from marionette_driver.errors import ElementNotVisibleException
 
 
+def inline(doc):
+    return "data:text/html;charset=utf-8,%s" % urllib.quote(doc)
+
+
 class TestTyping(MarionetteTestCase):
-
     def testShouldFireKeyPressEvents(self):
         test_html = self.marionette.absolute_url("javascriptPage.html")
         self.marionette.navigate(test_html)
         keyReporter = self.marionette.find_element("id", "keyReporter")
         keyReporter.send_keys("a")
         result = self.marionette.find_element("id", "result")
         self.assertTrue("press:" in result.text)
 
@@ -275,8 +280,22 @@ class TestTyping(MarionetteTestCase):
         # If we don't get an error below we are good
         self.marionette.find_element('tag name', 'body').send_keys('foo')
 
     def testShouldThrowElementNotVisibleWhenInputHidden(self):
         test_html = self.marionette.absolute_url("javascriptPage.html")
         self.marionette.navigate(test_html)
         not_displayed = self.marionette.find_element('id', 'notDisplayed')
         self.assertRaises(ElementNotVisibleException, not_displayed.send_keys, 'foo')
+
+    def test_appends_to_input_text(self):
+        self.marionette.navigate(inline("<input>"))
+        el = self.marionette.find_element("tag name", "input")
+        el.send_keys("foo")
+        el.send_keys("bar")
+        self.assertEqual("foobar", el.get_attribute("value"))
+
+    def test_appends_to_textarea(self):
+        self.marionette.navigate(inline("<textarea></textarea>"))
+        textarea = self.marionette.find_element("tag name", "textarea")
+        textarea.send_keys("foo")
+        textarea.send_keys("bar")
+        self.assertEqual("foobar", textarea.get_attribute("value"))
--- a/testing/marionette/sendkeys.js
+++ b/testing/marionette/sendkeys.js
@@ -121,27 +121,47 @@ function sendSingleKey (keyToSend, modif
     let modName = keyModifierNames[keyCode];
     modifiers[modName] = !modifiers[modName];
   } else if (modifiers.shiftKey) {
     keyCode = keyCode.toUpperCase();
   }
   utils.synthesizeKey(keyCode, modifiers, document);
 }
 
-function sendKeysToElement (document, element, keysToSend, successCallback, errorCallback, command_id, ignoreVisibility) {
+/**
+ * Focus element and, if a textual input field and no previous selection
+ * state exists, move the caret to the end of the input field.
+ *
+ * @param {Element} el
+ *     Element to focus.
+ */
+function focusElement(el) {
+  let t = el.type;
+  if (t && (t == "text" || t == "textarea")) {
+    if (el.selectionEnd == 0) {
+      let len = el.value.length;
+      el.setSelectionRange(len, len);
+    }
+  }
+  el.focus();
+}
+
+function sendKeysToElement(document, element, keysToSend, successCallback, errorCallback, command_id, ignoreVisibility) {
   if (ignoreVisibility || checkVisible(element)) {
-    element.focus();
+    focusElement(element);
+
     let modifiers = {
       shiftKey: false,
       ctrlKey: false,
       altKey: false,
       metaKey: false
     };
     let value = keysToSend.join("");
     for (var i = 0; i < value.length; i++) {
       var c = value.charAt(i);
       sendSingleKey(c, modifiers, document);
     }
+
     successCallback(command_id);
   } else {
     errorCallback(new ElementNotVisibleError("Element is not visible"), command_id);
   }
 };