Bug 1107706: Part 2: Add error module and WebDriver error objects
☠☠ backed out by c44d46087f59 ☠ ☠
authorAndreas Tolfsen <ato@mozilla.com>
Tue, 17 Mar 2015 14:27:20 +0000
changeset 264042 0c074cdc434e3c8ba412db44aece7b1840198fe5
parent 264041 3b449f8dd470a2cc866c302f527e20bedaadb786
child 264043 b0d00faceef4e348cc99c020f01d59c7933677b7
push id4718
push userraliiev@mozilla.com
push dateMon, 11 May 2015 18:39:53 +0000
treeherdermozilla-beta@c20c4ef55f08 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
bugs1107706
milestone39.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1107706: Part 2: Add error module and WebDriver error objects Adds the ability to throw error objects for WebDriver statuses, and an error module with convenience functions for manipulation of these and for handling other error related operations.
testing/marionette/error.js
testing/marionette/jar.mn
new file mode 100644
--- /dev/null
+++ b/testing/marionette/error.js
@@ -0,0 +1,314 @@
+/* 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/. */
+
+"use strict";
+
+const {utils: Cu} = Components;
+
+const errors = [
+  "ElementNotVisibleError",
+  "FrameSendFailureError",
+  "FrameSendNotInitializedError",
+  "JavaScriptError",
+  "NoAlertOpenError",
+  "NoSuchElementError",
+  "NoSuchFrameError",
+  "NoSuchWindowError",
+  "ScriptTimeoutError",
+  "SessionNotCreatedError",
+  "TimeoutError",
+  "UnknownCommandError",
+  "UnknownError",
+  "UnsupportedOperationError",
+  "WebDriverError",
+];
+
+this.EXPORTED_SYMBOLS = ["error"].concat(errors);
+
+this.error = {};
+
+error.toJSON = function(err) {
+  return {
+    message: err.message,
+    stacktrace: err.stack || null,
+    status: err.code
+  };
+};
+
+/**
+ * Gets WebDriver error by its Selenium status code number.
+ */
+error.byCode = n => lookup.get(n);
+
+/**
+ * Determines if the given status code is successful.
+ */
+error.isSuccess = code => code === 0;
+
+/**
+ * Old-style errors are objects that has all of the properties
+ * "message", "code", and "stack".
+ *
+ * When listener.js starts forwarding real errors by CPOW
+ * we can remove this.
+ */
+let isOldStyleError = function(obj) {
+  return typeof obj == "object" &&
+    ["message", "code", "stack"].every(c => obj.hasOwnProperty(c));
+}
+
+/**
+ * Checks if obj is an instance of the Error prototype in a safe manner.
+ * Prefer using this over using instanceof since the Error prototype
+ * isn't unique across browsers, and XPCOM exceptions are special
+ * snowflakes.
+ */
+error.isError = function(obj) {
+  if (obj === null || typeof obj != "object") {
+    return false;
+  // XPCOM exception.
+  // Object.getPrototypeOf(obj).result throws error,
+  // consequently we must do the check for properties in its
+  // prototypal chain (using in, instead of obj.hasOwnProperty) here.
+  } else if ("result" in obj) {
+    return true;
+  } else {
+    return Object.getPrototypeOf(obj) == "Error" || isOldStyleError(obj);
+  }
+};
+
+/**
+ * Checks if obj is an object in the WebDriverError prototypal chain.
+ */
+error.isWebDriverError = function(obj) {
+  return error.isError(obj) &&
+    (("name" in obj && errors.indexOf(obj.name) > 0) ||
+      isOldStyleError(obj));
+};
+
+/**
+ * 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)}`;
+  dump(msg + "\n");
+  if (Cu.reportError) {
+    Cu.reportError(msg);
+  }
+};
+
+/**
+ * Prettifies an instance of Error and its stacktrace to a string.
+ */
+error.stringify = function(err) {
+  try {
+    let s = err.toString();
+    if ("stack" in err) {
+      s += "\n" + err.stack;
+    }
+    return s;
+  } catch (e) {
+    return "<unprintable error>";
+  }
+};
+
+/**
+ * WebDriverError is the prototypal parent of all WebDriver errors.
+ * It should not be used directly, as it does not correspond to a real
+ * error in the specification.
+ */
+this.WebDriverError = function(msg) {
+  Error.call(this, msg);
+  this.name = "WebDriverError";
+  this.message = msg;
+  this.code = 500;  // overridden
+};
+WebDriverError.prototype = Object.create(Error.prototype);
+
+this.NoSuchElementError = function(msg) {
+  WebDriverError.call(this, msg);
+  this.name = "NoSuchElementError";
+  this.status = "no such element";
+  this.code = 7;
+};
+NoSuchElementError.prototype = Object.create(WebDriverError.prototype);
+
+this.NoSuchFrameError = function(msg) {
+  WebDriverError.call(this, msg);
+  this.name = "NoSuchFrameError";
+  this.status = "no such frame";
+  this.code = 8;
+};
+NoSuchFrameError.prototype = Object.create(WebDriverError.prototype);
+
+this.UnknownCommandError = function(msg) {
+  WebDriverError.call(this, msg);
+  this.name = "UnknownCommandError";
+  this.status = "unknown command";
+  this.code = 9;
+};
+UnknownCommandError.prototype = Object.create(WebDriverError.prototype);
+
+this.ElementNotVisibleError = function(msg) {
+  WebDriverError.call(this, msg);
+  this.name = "ElementNotVisibleError";
+  this.status = "element not visible";
+  this.code = 11;
+};
+ElementNotVisibleError.prototype = Object.create(WebDriverError.prototype);
+
+this.InvalidElementState = function(msg) {
+  WebDriverError.call(this, msg);
+  this.name = "InvalidElementState";
+  this.status = "invalid element state";
+  this.code = 12;
+};
+InvalidElementState.prototype = Object.create(WebDriverError.prototype);
+
+this.UnknownError = function(msg) {
+  WebDriverError.call(this, msg);
+  this.name = "UnknownError";
+  this.status = "unknown error";
+  this.code = 13;
+};
+UnknownError.prototype = Object.create(WebDriverError.prototype);
+
+/**
+ * Creates an error message for a JavaScript error thrown during
+ * executeScript or executeAsyncScript.
+ *
+ * @param {Error} err
+ *     An Error object passed to a catch block or a message.
+ * @param {string} fnName
+ *     The name of the function to use in the stack trace message
+ *     (e.g. execute_script).
+ * @param {string} file
+ *     The filename of the test file containing the Marionette
+ *     command that caused this error to occur.
+ * @param {number} line
+ *     The line number of the above test file.
+ * @param {string=} script
+ *     The JS script being executed in text form.
+ */
+this.JavaScriptError = function(err, fnName, file, line, script) {
+  let msg = String(err);
+  let trace = "";
+
+  if (fnName && line) {
+    trace += `${fnName} @${file}`;
+    if (line) {
+      trace += `, line ${line}`;
+    }
+  }
+
+  if (typeof err == "object" && "name" in err && "stack" in err) {
+    let jsStack = err.stack.split("\n");
+    let match = jsStack[0].match(/:(\d+):\d+$/);
+    let jsLine = match ? parseInt(match[1]) : 0;
+    if (script) {
+      let src = script.split("\n")[jsLine];
+      trace += "\n" +
+        "inline javascript, line " + jsLine + "\n" +
+        "src: \"" + src + "\"";
+    }
+  }
+
+  WebDriverError.call(this, msg);
+  this.name = "JavaScriptError";
+  this.status = "javascript error";
+  this.code = 17;
+  this.stack = trace;
+};
+JavaScriptError.prototype = Object.create(WebDriverError.prototype);
+
+this.TimeoutError = function(msg) {
+  WebDriverError.call(this, msg);
+  this.name = "TimeoutError";
+  this.status = "timeout";
+  this.code = 21;
+};
+TimeoutError.prototype = Object.create(WebDriverError.prototype);
+
+this.NoSuchWindowError = function(msg) {
+  WebDriverError.call(this, msg);
+  this.name = "NoSuchWindowError";
+  this.status = "no such window";
+  this.code = 23;
+};
+NoSuchWindowError.prototype = Object.create(WebDriverError.prototype);
+
+this.NoAlertOpenError = function(msg) {
+  WebDriverError.call(this, msg);
+  this.name = "NoAlertOpenError";
+  this.status = "no such alert";
+  this.code = 27;
+}
+NoAlertOpenError.prototype = Object.create(WebDriverError.prototype);
+
+this.ScriptTimeoutError = function(msg) {
+  WebDriverError.call(this, msg);
+  this.name = "ScriptTimeoutError";
+  this.status = "script timeout";
+  this.code = 28;
+};
+ScriptTimeoutError.prototype = Object.create(WebDriverError.prototype);
+
+this.SessionNotCreatedError = function(msg) {
+  WebDriverError.call(this, msg);
+  this.name = "SessionNotCreatedError";
+  this.status = "session not created";
+  // should be 33 to match Selenium
+  this.code = 71;
+}
+SessionNotCreatedError.prototype = Object.create(WebDriverError.prototype);
+
+this.FrameSendNotInitializedError = function(frame) {
+  this.message = "Error sending message to frame (NS_ERROR_NOT_INITIALIZED)";
+  WebDriverError.call(this, this.message);
+  this.name = "FrameSendNotInitializedError";
+  this.status = "frame send not initialized error";
+  this.code = 54;
+  this.frame = frame;
+  this.errMsg = `${this.message} ${this.frame}; frame has closed.`;
+};
+FrameSendNotInitializedError.prototype = Object.create(WebDriverError.prototype);
+
+this.FrameSendFailureError = function(frame) {
+  this.message = "Error sending message to frame (NS_ERROR_FAILURE)";
+  WebDriverError.call(this, this.message);
+  this.name = "FrameSendFailureError";
+  this.status = "frame send failure error";
+  this.code = 55;
+  this.frame = frame;
+  this.errMsg = `${this.message} ${this.frame}; frame not responding.`;
+};
+FrameSendFailureError.prototype = Object.create(WebDriverError.prototype);
+
+this.UnsupportedOperationError = function(msg) {
+  WebDriverError.call(this, msg);
+  this.name = "UnsupportedOperationError";
+  this.status = "unsupported operation";
+  this.code = 405;
+};
+UnsupportedOperationError.prototype = Object.create(WebDriverError.prototype);
+
+const errorObjs = [
+  this.ElementNotVisibleError,
+  this.FrameSendFailureError,
+  this.FrameSendNotInitializedError,
+  this.JavaScriptError,
+  this.NoAlertOpenError,
+  this.NoSuchElementError,
+  this.NoSuchFrameError,
+  this.NoSuchWindowError,
+  this.ScriptTimeoutError,
+  this.SessionNotCreatedError,
+  this.TimeoutError,
+  this.UnknownCommandError,
+  this.UnknownError,
+  this.UnsupportedOperationError,
+  this.WebDriverError,
+];
+const lookup = new Map(errorObjs.map(err => [new err().code, err]));
--- a/testing/marionette/jar.mn
+++ b/testing/marionette/jar.mn
@@ -9,16 +9,17 @@ marionette.jar:
   content/marionette-elements.js    (marionette-elements.js)
   content/marionette-sendkeys.js    (marionette-sendkeys.js)
   content/marionette-common.js      (marionette-common.js)
   content/marionette-actions.js     (marionette-actions.js)
   content/marionette-simpletest.js  (marionette-simpletest.js)
   content/marionette-frame-manager.js  (marionette-frame-manager.js)
   content/EventUtils.js  (EventUtils.js)
   content/ChromeUtils.js  (ChromeUtils.js)
+  content/error.js (error.js)
 #ifdef ENABLE_TESTS
   content/test.xul  (client/marionette/chrome/test.xul)
   content/test2.xul  (client/marionette/chrome/test2.xul)
   content/test_nested_iframe.xul  (client/marionette/chrome/test_nested_iframe.xul)
   content/test_anonymous_content.xul  (client/marionette/chrome/test_anonymous_content.xul)
 #endif
 
 % content specialpowers %content/