Bug 1107706: Part 2: Add error module and WebDriver error objects
authorAndreas Tolfsen <ato@mozilla.com>
Tue, 17 Mar 2015 14:27:20 +0000
changeset 266307 42f14f453f1ffa7cb4590f7f0b7c16fb43c97740
parent 266306 31df2186195a474ae202f33a6bfef3167a0190dc
child 266308 a76f6b26484d35189d533b8b3a6f0323304d5078
push id830
push userraliiev@mozilla.com
push dateFri, 19 Jun 2015 19:24:37 +0000
treeherdermozilla-release@932614382a68 [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/