Bug 1362992 - Wrap implementation errors as UnknownError
JavaScript errors that arise from the Marionette implementation are
currently wrapped as WebDriverErrors. A WebDriverError is encoded with
a "webdriver error" error code, which officially does not exist in the
WebDriver specification.
To distinguish WebDriver errors from programming errors of Marionette,
this changes them to be encoded as UnknownErrors (error code
"unknown error"). This will make stacktraces coming from clients
easier to read, since it is clear that the error is not caught by the
WebDriverError-catchall.
MozReview-Commit-ID: A5HejTOgOp8
--- a/testing/marionette/error.js
+++ b/testing/marionette/error.js
@@ -91,25 +91,34 @@ error.isError = function (val) {
* Checks if obj is an object in the WebDriverError prototypal chain.
*/
error.isWebDriverError = function (obj) {
return error.isError(obj) &&
("name" in obj && ERRORS.has(obj.name));
};
/**
- * Wraps any error as a WebDriverError. If the given error is already in
- * the WebDriverError prototype chain, this function returns it
- * unmodified.
+ * Ensures error instance is a WebDriverError.
+ *
+ * If the given error is already in the WebDriverError prototype
+ * chain, |err| is returned unmodified. If it is not, it is wrapped
+ * in UnknownError.
+ *
+ * @param {Error} err
+ * Error to conditionally turn into a WebDriverError.
+ *
+ * @return {WebDriverError}
+ * If |err| is a WebDriverError, it is returned unmodified.
+ * Otherwise an UnknownError type is returned.
*/
error.wrap = function (err) {
if (error.isWebDriverError(err)) {
return err;
}
- return new WebDriverError(err);
+ return new UnknownError(err);
};
/**
* Unhandled error reporter. Dumps the error and its stacktrace to console,
* and reports error to the Browser Console.
*/
error.report = function (err) {
let msg = "Marionette threw an error: " + error.stringify(err);
--- a/testing/marionette/test_error.js
+++ b/testing/marionette/test_error.js
@@ -51,26 +51,26 @@ add_test(function test_isWebDriverError(
add_test(function test_wrap() {
// webdriver-derived errors should not be wrapped
equal(error.wrap(new WebDriverError()).name, "WebDriverError");
ok(error.wrap(new WebDriverError()) instanceof WebDriverError);
equal(error.wrap(new InvalidArgumentError()).name, "InvalidArgumentError");
ok(error.wrap(new InvalidArgumentError()) instanceof WebDriverError);
ok(error.wrap(new InvalidArgumentError()) instanceof InvalidArgumentError);
- // JS errors should be wrapped in WebDriverError
- equal(error.wrap(new Error()).name, "WebDriverError");
- ok(error.wrap(new Error()) instanceof WebDriverError);
- equal(error.wrap(new EvalError()).name, "WebDriverError");
- equal(error.wrap(new InternalError()).name, "WebDriverError");
- equal(error.wrap(new RangeError()).name, "WebDriverError");
- equal(error.wrap(new ReferenceError()).name, "WebDriverError");
- equal(error.wrap(new SyntaxError()).name, "WebDriverError");
- equal(error.wrap(new TypeError()).name, "WebDriverError");
- equal(error.wrap(new URIError()).name, "WebDriverError");
+ // JS errors should be wrapped in UnknownError
+ equal(error.wrap(new Error()).name, "UnknownError");
+ ok(error.wrap(new Error()) instanceof UnknownError);
+ equal(error.wrap(new EvalError()).name, "UnknownError");
+ equal(error.wrap(new InternalError()).name, "UnknownError");
+ equal(error.wrap(new RangeError()).name, "UnknownError");
+ equal(error.wrap(new ReferenceError()).name, "UnknownError");
+ equal(error.wrap(new SyntaxError()).name, "UnknownError");
+ equal(error.wrap(new TypeError()).name, "UnknownError");
+ equal(error.wrap(new URIError()).name, "UnknownError");
// wrapped JS errors should retain their type
// as part of the message field
equal(error.wrap(new WebDriverError("foo")).message, "foo");
equal(error.wrap(new TypeError("foo")).message, "TypeError: foo");
run_next_test();
});
--- a/testing/marionette/test_message.js
+++ b/testing/marionette/test_message.js
@@ -145,30 +145,61 @@ add_test(function test_Response_send() {
let resp = new Response(42, () => fired = true);
resp.send();
equal(true, resp.sent);
equal(true, fired);
run_next_test();
});
-add_test(function test_Response_sendError() {
- let err = new WebDriverError();
+add_test(function test_Response_sendError_sent() {
+ let resp = new Response(42, r => equal(false, r.sent));
+ resp.sendError(new WebDriverError());
+ ok(resp.sent);
+ Assert.throws(() => resp.send(), /already been sent/);
+
+ run_next_test();
+});
+
+add_test(function test_Response_sendError_body() {
+ let resp = new Response(42, r => equal(null, r.body));
+ resp.sendError(new WebDriverError());
+
+ run_next_test();
+});
+
+add_test(function test_Response_sendError_errorSerialisation() {
+ let err1 = new WebDriverError();
+ let resp1 = new Response(42);
+ resp1.sendError(err1);
+ equal(err1.status, resp1.error.error);
+ deepEqual(err1.toJSON(), resp1.error);
+
+ let err2 = new InvalidArgumentError();
+ let resp2 = new Response(43);
+ resp2.sendError(err2);
+ equal(err2.status, resp2.error.error);
+ deepEqual(err2.toJSON(), resp2.error);
+
+ run_next_test();
+});
+
+add_test(function test_Response_sendError_wrapInternalError() {
+ let err = new ReferenceError("foo");
+
+ // errors that originate from JavaScript (i.e. Marionette implementation
+ // issues) should be converted to UnknownError for transport
let resp = new Response(42, r => {
- equal(err.toJSON().error, r.error.error);
- equal(null, r.body);
- equal(false, r.sent);
+ equal("unknown error", r.error.error);
+ equal(false, resp.sent);
});
- resp.sendError(err);
+ // they should also throw after being sent
+ Assert.throws(() => resp.sendError(err), /foo/);
equal(true, resp.sent);
- Assert.throws(() => resp.send(), /already been sent/);
-
- resp.sent = false;
- Assert.throws(() => resp.sendError(new Error()));
run_next_test();
});
add_test(function test_Response_toMsg() {
let resp = new Response(42, () => {});
let msg = resp.toMsg();