Bug 1430575 - Adjust editable definition to match WebDriver. r?automatedtester draft
authorAndreas Tolfsen <ato@sny.no>
Mon, 15 Jan 2018 17:14:37 +0000
changeset 721570 2e45e00dc9ae1564b37d9454b4bd855ee26af9db
parent 721495 4e429d313fd2e0f9202271ee8f3fb798817ec3e7
child 721571 5dd13cfe669f632d556f267543695af019b92442
push id95875
push userbmo:ato@sny.no
push dateWed, 17 Jan 2018 13:45:56 +0000
reviewersautomatedtester
bugs1430575
milestone59.0a1
Bug 1430575 - Adjust editable definition to match WebDriver. r?automatedtester Introduces a new function, isMutableFormControl, to the element module in Marionette that tests if an element is a form control that can be edited by the user. This replaces the proprietary UNEDITABLE_INPUTS set used previously. An editable element is, according to the WebDriver standard, an element which belongs to the two subcategories of editable elements. This patch implements the first category of the mutable form controls. MozReview-Commit-ID: Aix19mq3lcb
testing/marionette/element.js
testing/marionette/test_element.js
--- a/testing/marionette/element.js
+++ b/testing/marionette/element.js
@@ -29,25 +29,16 @@ this.EXPORTED_SYMBOLS = [
 
 const {
   FIRST_ORDERED_NODE_TYPE,
   ORDERED_NODE_ITERATOR_TYPE,
 } = Ci.nsIDOMXPathResult;
 const ELEMENT_NODE = 1;
 const DOCUMENT_NODE = 9;
 
-const UNEDITABLE_INPUTS = new Set([
-  "checkbox",
-  "radio",
-  "hidden",
-  "submit",
-  "button",
-  "image",
-]);
-
 const XBLNS = "http://www.mozilla.org/xbl";
 const XULNS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
 
 /** XUL elements that support checked property. */
 const XUL_CHECKED_ELS = new Set([
   "button",
   "checkbox",
   "listitem",
@@ -839,16 +830,70 @@ element.isDisabled = function(el) {
       return el.disabled;
 
     default:
       return false;
   }
 };
 
 /**
+ * Denotes elements that can be used for typing and clearing.
+ *
+ * Elements that are considered WebDriver-editable are non-readonly
+ * and non-disabled <code>&lt;input&gt;</code> elements in the Text,
+ * Search, URL, Telephone, Email, Password, Date, Month, Date and
+ * Time Local, Number, Range, Color, and File Upload states, and
+ * <code>&lt;textarea&gt;</code> elements.
+ *
+ * @param {Element} el
+ *     Element to test.
+ *
+ * @return {boolean}
+ *     True if editable, false otherwise.
+ */
+element.isMutableFormControl = function(el) {
+  if (!element.isDOMElement(el)) {
+    return false;
+  }
+  if (element.isReadOnly(el) || element.isDisabled(el)) {
+    return false;
+  }
+
+  if (el.localName == "textarea") {
+    return true;
+  }
+
+  if (el.localName != "input") {
+    return false;
+  }
+
+  switch (el.type) {
+    case "color":
+    case "date":
+    case "datetime-local":
+    case "email":
+    case "file":
+    case "month":
+    case "number":
+    case "password":
+    case "range":
+    case "search":
+    case "tel":
+    case "text":
+    case "time":
+    case "url":
+    case "week":
+      return true;
+
+    default:
+      return false;
+  }
+};
+
+/**
  * An editing host is a node that is either an HTML element with a
  * <code>contenteditable</code> attribute, or the HTML element child
  * of a document whose <code>designMode</code> is enabled.
  *
  * @param {Element} el
  *     Element to determine if is an editing host.
  *
  * @return {boolean}
@@ -887,19 +932,17 @@ element.isEditable = function(el) {
   if (!element.isDOMElement(el)) {
     return false;
   }
 
   if (element.isReadOnly(el) || element.isDisabled(el)) {
     return false;
   }
 
-  return (el.localName == "input" && !UNEDITABLE_INPUTS.has(el.type)) ||
-      el.localName == "textarea" ||
-      element.isEditingHost(el);
+  return element.isMutableFormControl(el) || element.isEditingHost(el);
 };
 
 /**
  * This function generates a pair of coordinates relative to the viewport
  * given a target element and coordinates relative to that element's
  * top-left corner.
  *
  * @param {Node} node
--- a/testing/marionette/test_element.js
+++ b/testing/marionette/test_element.js
@@ -39,20 +39,25 @@ class Element {
     return tags.includes(this.localName);
   }
 }
 
 class DOMElement extends Element {
   constructor(tagName, attrs = {}) {
     super(tagName, attrs);
 
-    this.namespaceURI = XHTMLNS;
+    if (typeof this.namespaceURI == "undefined") {
+      this.namespaceURI = XHTMLNS;
+    }
     if (typeof this.ownerDocument == "undefined") {
       this.ownerDocument = {designMode: "off"};
     }
+    if (typeof this.type == "undefined") {
+      this.type = "text";
+    }
 
     if (this.localName == "option") {
       this.selected = false;
     }
 
     if (this.localName == "input" && ["checkbox", "radio"].includes(this.type)) {
       this.checked = false;
     }
@@ -253,16 +258,50 @@ add_test(function test_isEditable() {
 
   ok(element.isEditable(new DOMElement("textarea")));
   ok(element.isEditable(new DOMElement("p", {ownerDocument: {designMode: "on"}})));
   ok(element.isEditable(new DOMElement("p", {isContentEditable: true})));
 
   run_next_test();
 });
 
+add_test(function test_isMutableFormControlElement() {
+  ok(!element.isMutableFormControl(null));
+  ok(!element.isMutableFormControl(new DOMElement("textarea", {readOnly: true})));
+  ok(!element.isMutableFormControl(new DOMElement("textarea", {disabled: true})));
+
+  const mutableStates = new Set([
+    "color",
+    "date",
+    "datetime-local",
+    "email",
+    "file",
+    "month",
+    "number",
+    "password",
+    "range",
+    "search",
+    "tel",
+    "text",
+    "url",
+    "week",
+  ]);
+  for (let type of mutableStates) {
+    ok(element.isMutableFormControl(new DOMElement("input", {type})));
+  }
+  ok(element.isMutableFormControl(new DOMElement("textarea")));
+
+  ok(!element.isMutableFormControl(new DOMElement("input", {type: "hidden"})));
+  ok(!element.isMutableFormControl(new DOMElement("p")));
+  ok(!element.isMutableFormControl(new DOMElement("p", {isContentEditable: true})));
+  ok(!element.isMutableFormControl(new DOMElement("p", {ownerDocument: {designMode: "on"}})));
+
+  run_next_test();
+});
+
 add_test(function test_coordinates() {
   let p = element.coordinates(domEl);
   ok(p.hasOwnProperty("x"));
   ok(p.hasOwnProperty("y"));
   equal("number", typeof p.x);
   equal("number", typeof p.y);
 
   deepEqual({x: 50, y: 50}, element.coordinates(domEl));