Bug 1272653 - Make Get Element Attribute spec compatible; r?automatedtester
MozReview-Commit-ID: K5PsnmRrDJk
--- a/testing/marionette/element.js
+++ b/testing/marionette/element.js
@@ -31,16 +31,17 @@ const logger = Log.repository.getLogger(
*/
this.EXPORTED_SYMBOLS = [
"element",
"ElementManager",
];
const DOCUMENT_POSITION_DISCONNECTED = 1;
+const XMLNS = "http://www.w3.org/1999/xhtml";
const uuidGen = Cc["@mozilla.org/uuid-generator;1"]
.getService(Ci.nsIUUIDGenerator);
this.element = {};
element.LegacyKey = "ELEMENT";
element.Key = "element-6066-11e4-a52e-4f735466cecf";
@@ -906,8 +907,59 @@ element.getInteractableElementTree = fun
element.isKeyboardInteractable = function(el) {
return true;
};
element.isXULElement = function(el) {
let ns = atom.getElementAttribute(el, "namespaceURI");
return ns.indexOf("there.is.only.xul") >= 0;
};
+
+const boolEls = {
+ audio: ["autoplay", "controls", "loop", "muted"],
+ button: ["autofocus", "disabled", "formnovalidate"],
+ details: ["open"],
+ dialog: ["open"],
+ fieldset: ["disabled"],
+ form: ["novalidate"],
+ iframe: ["allowfullscreen"],
+ img: ["ismap"],
+ input: ["autofocus", "checked", "disabled", "formnovalidate", "multiple", "readonly", "required"],
+ keygen: ["autofocus", "disabled"],
+ menuitem: ["checked", "default", "disabled"],
+ object: ["typemustmatch"],
+ ol: ["reversed"],
+ optgroup: ["disabled"],
+ option: ["disabled", "selected"],
+ script: ["async", "defer"],
+ select: ["autofocus", "disabled", "multiple", "required"],
+ textarea: ["autofocus", "disabled", "readonly", "required"],
+ track: ["default"],
+ video: ["autoplay", "controls", "loop", "muted"],
+};
+
+/**
+ * Tests if the attribute is a boolean attribute on element.
+ *
+ * @param {DOMElement} el
+ * Element to test if |attr| is a boolean attribute on.
+ * @param {string} attr
+ * Attribute to test is a boolean attribute.
+ *
+ * @return {boolean}
+ * True if the attribute is boolean, false otherwise.
+ */
+element.isBooleanAttribute = function(el, attr) {
+ if (el.namespaceURI !== XMLNS) {
+ return false;
+ }
+
+ // global boolean attributes that apply to all HTML elements,
+ // except for custom elements
+ if ((attr == "hidden" || attr == "itemscope") && !el.localName.includes("-")) {
+ return true;
+ }
+
+ if (!boolEls.hasOwnProperty(el.localName)) {
+ return false;
+ }
+ return boolEls[el.localName].includes(attr)
+};
--- a/testing/marionette/harness/marionette/tests/unit/test_element_state.py
+++ b/testing/marionette/harness/marionette/tests/unit/test_element_state.py
@@ -1,20 +1,58 @@
# 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 types
import urllib
from marionette import MarionetteTestCase
from marionette_driver.by import By
-def inline(doc):
- return "data:text/html;charset=utf-8,%s" % urllib.quote(doc)
+boolean_attributes = {
+ "audio": ["autoplay", "controls", "loop", "muted"],
+ "button": ["autofocus", "disabled", "formnovalidate"],
+ "details": ["open"],
+ "dialog": ["open"],
+ "fieldset": ["disabled"],
+ "form": ["novalidate"],
+ "iframe": ["allowfullscreen"],
+ "img": ["ismap"],
+ "input": ["autofocus", "checked", "disabled", "formnovalidate", "multiple", "readonly", "required"],
+ "menuitem": ["checked", "default", "disabled"],
+ "object": ["typemustmatch"],
+ "ol": ["reversed"],
+ "optgroup": ["disabled"],
+ "option": ["disabled", "selected"],
+ "script": ["async", "defer"],
+ "select": ["autofocus", "disabled", "multiple", "required"],
+ "textarea": ["autofocus", "disabled", "readonly", "required"],
+ "track": ["default"],
+ "video": ["autoplay", "controls", "loop", "muted"],
+}
+
+
+def inline(doc, doctype="html"):
+ if doctype == "html":
+ return "data:text/html;charset=utf-8,%s" % urllib.quote(doc)
+ elif doctype == "xhtml":
+ return "data:application/xhtml+xml,%s" % urllib.quote(
+r"""<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+ <head>
+ <title>XHTML might be the future</title>
+ </head>
+
+ <body>
+ %s
+ </body>
+</html>""" % doc)
attribute = inline("<input foo=bar>")
input = inline("<input>")
disabled = inline("<input disabled=baz>")
check = inline("<input type=checkbox>")
@@ -34,27 +72,66 @@ class TestIsElementDisplayed(MarionetteT
self.marionette.navigate(test_html)
l = self.marionette.find_element(By.NAME, "myCheckBox")
self.assertTrue(l.is_displayed())
self.marionette.execute_script("arguments[0].hidden = true;", [l])
self.assertFalse(l.is_displayed())
class TestGetElementAttribute(MarionetteTestCase):
- def test_get(self):
- test_html = self.marionette.absolute_url("test.html")
- self.marionette.navigate(test_html)
- l = self.marionette.find_element(By.ID, "mozLink")
- self.assertEqual("mozLink", l.get_attribute("id"))
+ def test_normal_attribute(self):
+ self.marionette.navigate(inline("<p style=foo>"))
+ el = self.marionette.find_element(By.TAG_NAME, "p")
+ attr = el.get_attribute("style")
+ self.assertIsInstance(attr, types.StringTypes)
+ self.assertEqual("foo", attr)
+
+ def test_boolean_attributes(self):
+ for tag, attrs in boolean_attributes.iteritems():
+ for attr in attrs:
+ print("testing boolean attribute <%s %s>" % (tag, attr))
+ doc = inline("<%s %s>" % (tag, attr))
+ self.marionette.navigate(doc)
+ el = self.marionette.find_element(By.TAG_NAME, tag)
+ res = el.get_attribute(attr)
+ self.assertIsInstance(res, types.StringTypes)
+ self.assertEqual("true", res)
+
+ def test_global_boolean_attributes(self):
+ self.marionette.navigate(inline("<p hidden>foo"))
+ el = self.marionette.find_element(By.TAG_NAME, "p")
+ attr = el.get_attribute("hidden")
+ self.assertIsInstance(attr, types.StringTypes)
+ self.assertEqual("true", attr)
- def test_boolean(self):
- test_html = self.marionette.absolute_url("html5/boolean_attributes.html")
- self.marionette.navigate(test_html)
- disabled = self.marionette.find_element(By.ID, "disabled")
- self.assertEqual('true', disabled.get_attribute("disabled"))
+ self.marionette.navigate(inline("<p>foo"))
+ el = self.marionette.find_element(By.TAG_NAME, "p")
+ attr = el.get_attribute("hidden")
+ self.assertIsNone(attr)
+
+ self.marionette.navigate(inline("<p itemscope>foo"))
+ el = self.marionette.find_element(By.TAG_NAME, "p")
+ attr = el.get_attribute("itemscope")
+ self.assertIsInstance(attr, types.StringTypes)
+ self.assertEqual("true", attr)
+
+ self.marionette.navigate(inline("<p>foo"))
+ el = self.marionette.find_element(By.TAG_NAME, "p")
+ attr = el.get_attribute("itemscope")
+ self.assertIsNone(attr)
+
+ # TODO(ato): Test for custom elements
+
+ def test_xhtml(self):
+ doc = inline("<p hidden=\"true\">foo</p>", doctype="xhtml")
+ self.marionette.navigate(doc)
+ el = self.marionette.find_element(By.TAG_NAME, "p")
+ attr = el.get_attribute("hidden")
+ self.assertIsInstance(attr, types.StringTypes)
+ self.assertEqual("true", attr)
class TestGetElementProperty(MarionetteTestCase):
def test_get(self):
self.marionette.navigate(disabled)
el = self.marionette.find_element(By.TAG_NAME, "input")
prop = el.get_property("disabled")
self.assertIsInstance(prop, bool)
--- a/testing/marionette/listener.js
+++ b/testing/marionette/listener.js
@@ -1056,17 +1056,25 @@ function clickElement(id) {
return interaction.clickElement(
el,
!!capabilities.raisesAccessibilityExceptions,
capabilities.specificationLevel >= 1);
}
function getElementAttribute(id, name) {
let el = elementManager.getKnownElement(id, curContainer);
- return atom.getElementAttribute(el, name, curContainer.frame);
+ if (element.isBooleanAttribute(el, name)) {
+ if (el.hasAttribute(name)) {
+ return "true";
+ } else {
+ return null;
+ }
+ } else {
+ return el.getAttribute(name);
+ }
}
function getElementProperty(id, name) {
let el = elementManager.getKnownElement(id, curContainer);
return el[name];
}
/**