Bug 1432864 - Run native focussing steps on interaction commands. r?automatedtester draft
authorAndreas Tolfsen <ato@sny.no>
Wed, 24 Jan 2018 19:04:30 +0000
changeset 737945 86d6c2cff3948faf38da491ebbdacf14967d92d8
parent 737944 085aec3a9ee7683a5c1412dc044b3bc5acfdff00
child 737946 806a0eb4d34af6868e9b83556060b4bb892c5129
push id96803
push userbmo:ato@sny.no
push dateThu, 25 Jan 2018 12:02:16 +0000
reviewersautomatedtester
bugs1432864
milestone60.0a1
Bug 1432864 - Run native focussing steps on interaction commands. r?automatedtester Instead of generating custom focus events when interacting with elements, we can run the HTMLElement.focus() function will do the correct thing. Before this patch we only simulated focus events, whereas this patch will actually focus the element. MozReview-Commit-ID: IoBV2ngqOA5
testing/marionette/event.js
testing/marionette/interaction.js
testing/web-platform/tests/webdriver/tests/interaction/element_clear.py
testing/web-platform/tests/webdriver/tests/support/asserts.py
--- a/testing/marionette/event.js
+++ b/testing/marionette/event.js
@@ -1375,35 +1375,16 @@ event.sendEvent = function(eventType, el
   ev.metaKey = modifiers.meta;
   ev.altKey = modifiers.alt;
   ev.ctrlKey = modifiers.ctrl;
 
   ev.initEvent(eventType, opts.canBubble, true);
   el.dispatchEvent(ev);
 };
 
-event.focus = function(el, opts = {}) {
-  opts.canBubble = opts.canBubble || true;
-  let doc = el.ownerDocument || el.document;
-  let win = doc.defaultView;
-
-  let ev = new win.FocusEvent(el);
-  ev.initEvent("focus", opts.canBubble, true);
-  el.dispatchEvent(ev);
-};
-
-event.blur = function(el, {canBubble = true} = {}) {
-  let doc = el.ownerDocument || el.document;
-  let win = doc.defaultView;
-
-  let ev = new win.FocusEvent(el);
-  ev.initEvent("blur", canBubble, true);
-  el.dispatchEvent(ev);
-};
-
 event.mouseover = function(el, modifiers = {}, opts = {}) {
   return event.sendEvent("mouseover", el, modifiers, opts);
 };
 
 event.mousemove = function(el, modifiers = {}, opts = {}) {
   return event.sendEvent("mousemove", el, modifiers, opts);
 };
 
--- a/testing/marionette/interaction.js
+++ b/testing/marionette/interaction.js
@@ -265,17 +265,17 @@ interaction.selectOption = function(el) 
     throw new TypeError(pprint`Expected <option> element, got ${el}`);
   }
 
   let containerEl = element.getContainer(el);
 
   event.mouseover(containerEl);
   event.mousemove(containerEl);
   event.mousedown(containerEl);
-  event.focus(containerEl);
+  containerEl.focus();
 
   if (!el.disabled) {
     // Clicking <option> in <select> should not be deselected if selected.
     // However, clicking one in a <select multiple> should toggle
     // selectedness the way holding down Control works.
     if (containerEl.multiple) {
       el.selected = !el.selected;
     } else if (!el.selected) {
@@ -335,20 +335,20 @@ interaction.clearElement = function(el) 
     clearResettableElement(el);
   }
 };
 
 function clearContentEditableElement(el) {
   if (el.innerHTML === "") {
     return;
   }
-  event.focus(el);
+  el.focus();
   el.innerHTML = "";
   event.change(el);
-  event.blur(el);
+  el.blur();
 }
 
 function clearResettableElement(el) {
   if (!element.isMutableFormControl(el)) {
     throw new InvalidElementStateError(pprint`Not an editable form control: ${el}`);
   }
 
   let isEmpty;
@@ -361,20 +361,20 @@ function clearResettableElement(el) {
       isEmpty = el.value === "";
       break;
   }
 
   if (el.validity.valid && isEmpty) {
     return;
   }
 
-  event.focus(el);
+  el.focus();
   el.value = "";
   event.change(el);
-  event.blur(el);
+  el.blur();
 }
 
 /**
  * Waits until the event loop has spun enough times to process the
  * DOM events generated by clicking an element, or until the document
  * is unloaded.
  *
  * @param {Element} el
@@ -486,17 +486,17 @@ interaction.uploadFile = async function(
   fs.push(file);
 
   // <input type=file> opens OS widget dialogue
   // which means the mousedown/focus/mouseup/click events
   // occur before the change event
   event.mouseover(el);
   event.mousemove(el);
   event.mousedown(el);
-  event.focus(el);
+  el.focus();
   event.mouseup(el);
   event.click(el);
 
   el.mozSetFileArray(fs);
 
   event.change(el);
 };
 
--- a/testing/web-platform/tests/webdriver/tests/interaction/element_clear.py
+++ b/testing/web-platform/tests/webdriver/tests/interaction/element_clear.py
@@ -1,11 +1,15 @@
 import pytest
 
-from tests.support.asserts import assert_error, assert_success
+from tests.support.asserts import (
+    assert_element_has_focus,
+    assert_error,
+    assert_success,
+)
 from tests.support.inline import inline
 
 
 def add_event_listeners(element):
     element.session.execute_script("""
         let [target] = arguments;
         window.events = [];
         for (let expected of ["focus", "blur", "change"]) {
@@ -103,16 +107,17 @@ def test_input(session, type, value, def
 
     response = element_clear(session, element)
     assert_success(response)
     assert element.property("value") == default
     events = get_events(session)
     assert "focus" in events
     assert "change" in events
     assert "blur" in events
+    assert_element_has_focus(session.execute_script("return document.body"))
 
 
 @pytest.mark.parametrize("type",
                          ["number",
                           "range",
                           "email",
                           "password",
                           "search",
@@ -257,28 +262,30 @@ def test_contenteditable(session):
     element = session.find.css("p", all=False)
     add_event_listeners(element)
     assert element.property("innerHTML") == "foobar"
 
     response = element_clear(session, element)
     assert_success(response)
     assert element.property("innerHTML") == ""
     assert get_events(session) == ["focus", "change", "blur"]
+    assert_element_has_focus(session.execute_script("return document.body"))
 
 
 
 def test_designmode(session):
     session.url = inline("foobar")
     element = session.find.css("body", all=False)
     assert element.property("innerHTML") == "foobar"
     session.execute_script("document.designMode = 'on'")
 
     response = element_clear(session, element)
     assert_success(response)
     assert element.property("innerHTML") == "<br>"
+    assert_element_has_focus(session.execute_script("return document.body"))
 
 
 def test_resettable_element_focus_when_empty(session):
     session.url = inline("<input>")
     element = session.find.css("input", all=False)
     add_event_listeners(element)
     assert element.property("value") == ""
 
--- a/testing/web-platform/tests/webdriver/tests/support/asserts.py
+++ b/testing/web-platform/tests/webdriver/tests/support/asserts.py
@@ -132,8 +132,19 @@ def assert_same_element(session, a, b):
     try:
         a_markup = session.execute_script("return arguments[0].outerHTML;", args=(a,))
         b_markup = session.execute_script("return arguments[0].outerHTML;", args=(b,))
         message += " Actual: `%s`. Expected: `%s`." % (a_markup, b_markup)
     except WebDriverException:
         pass
 
     raise AssertionError(message)
+
+
+def assert_element_has_focus(target_element):
+    session = target_element.session
+
+    active_element = session.execute_script("return document.activeElement")
+    active_tag = active_element.property("localName")
+    target_tag = target_element.property("localName")
+
+    assert active_element == target_element, (
+        "Focussed element is <%s>, not <%s>" % (active_tag, target_tag))