Bug 1333014 - Pretty-print HTML elements; r?maja_zf
When passing a DOM element that is an HTML element to error.pprint,
it will get pretty-printed with its ID and class properties.
This helps to identify elements when one is obscuring the other when
clicking. For example, the error message
Element <input id="foo"> is obscured by <input id="bar">
is nicer than the old error message
Element [object HTMLElement] {} is obscured by [object HTMLElement] {}
MozReview-Commit-ID: 8U2Lo8V4lmv
--- a/testing/marionette/element.js
+++ b/testing/marionette/element.js
@@ -175,18 +175,19 @@ element.Store = class {
wrappedShadowRoot = new XPCNativeWrapper(container.shadowRoot);
}
let wrappedEl = new XPCNativeWrapper(el);
if (!el ||
!(wrappedEl.ownerDocument == wrappedFrame.document) ||
element.isDisconnected(wrappedEl, wrappedFrame, wrappedShadowRoot)) {
throw new StaleElementReferenceError(
- "The element reference is stale. Either the element " +
- "is no longer attached to the DOM or the page has been refreshed.");
+ error.pprint`The element reference of ${el} stale: ` +
+ "either the element is no longer attached to the DOM " +
+ "or the page has been refreshed");
}
return el;
}
};
/**
* Find a single element or a collection of elements starting at the
--- a/testing/marionette/error.js
+++ b/testing/marionette/error.js
@@ -120,32 +120,70 @@ error.stringify = function (err) {
}
};
/**
* Pretty-print values passed to template strings.
*
* Usage:
*
- * let input = {value: true};
- * error.pprint`Expected boolean, got ${input}`;
- * => "Expected boolean, got [object Object] {"value": true}"
+ * let bool = {value: true};
+ * error.pprint`Expected boolean, got ${bool}`;
+ * => 'Expected boolean, got [object Object] {"value": true}'
+ *
+ * let htmlElement = document.querySelector("input#foo");
+ * error.pprint`Expected element ${htmlElement}`;
+ * => 'Expected element <input id="foo" class="bar baz">'
*/
-error.pprint = function (strings, ...values) {
+error.pprint = function (ss, ...values) {
+ function prettyObject (obj) {
+ let proto = Object.prototype.toString.call(obj);
+ let s = "";
+ try {
+ s = JSON.stringify(obj);
+ } catch (e if e instanceof TypeError) {
+ s = `<${e.message}>`;
+ }
+ return proto + " " + s;
+ }
+
+ function prettyElement (el) {
+ let ident = [];
+ if (el.id) {
+ ident.push(`id="${el.id}"`);
+ }
+ if (el.classList.length > 0) {
+ ident.push(`class="${el.className}"`);
+ }
+
+ let idents = "";
+ if (ident.length > 0) {
+ idents = " " + ident.join(" ");
+ }
+
+ return `<${el.localName}${idents}>`;
+ }
+
let res = [];
- for (let i = 0; i < strings.length; i++) {
- res.push(strings[i]);
+ for (let i = 0; i < ss.length; i++) {
+ res.push(ss[i]);
if (i < values.length) {
let val = values[i];
- res.push(Object.prototype.toString.call(val));
- let s = JSON.stringify(val);
- if (s && s.length > 0) {
- res.push(" ");
- res.push(s);
+ let typ = Object.prototype.toString.call(val);
+ let s;
+ try {
+ if (val && val.nodeType === 1) {
+ s = prettyElement(val);
+ } else {
+ s = prettyObject(val);
+ }
+ } catch (e) {
+ s = typeof val;
}
+ res.push(s);
}
}
return res.join("");
};
/**
* WebDriverError is the prototypal parent of all WebDriver errors.
* It should not be used directly, as it does not correspond to a real
--- a/testing/marionette/test_error.js
+++ b/testing/marionette/test_error.js
@@ -84,16 +84,43 @@ add_test(function test_stringify() {
equal("WebDriverError: foo",
error.stringify(new WebDriverError("foo")).split("\n")[0]);
equal("InvalidArgumentError: foo",
error.stringify(new InvalidArgumentError("foo")).split("\n")[0]);
run_next_test();
});
+add_test(function test_pprint() {
+ equal('[object Object] {"foo":"bar"}', error.pprint`${{foo: "bar"}}`);
+
+ equal("[object Number] 42", error.pprint`${42}`);
+ equal("[object Boolean] true", error.pprint`${true}`);
+ equal("[object Undefined] undefined", error.pprint`${undefined}`);
+ equal("[object Null] null", error.pprint`${null}`);
+
+ let complexObj = {toJSON: () => "foo"};
+ equal('[object Object] "foo"', error.pprint`${complexObj}`);
+
+ let cyclic = {};
+ cyclic.me = cyclic;
+ equal("[object Object] <cyclic object value>", error.pprint`${cyclic}`);
+
+ let el = {
+ nodeType: 1,
+ localName: "input",
+ id: "foo",
+ classList: {length: 1},
+ className: "bar baz",
+ };
+ equal('<input id="foo" class="bar baz">', error.pprint`${el}`);
+
+ run_next_test();
+});
+
add_test(function test_toJSON() {
let e0 = new WebDriverError();
let e0s = e0.toJSON();
equal(e0s.error, "webdriver error");
equal(e0s.message, "");
equal(e0s.stacktrace, e0.stack);
let e1 = new WebDriverError("a");