2015-03-17 14:27:20 +00:00
|
|
|
/* 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";
|
|
|
|
|
2016-01-29 12:57:46 +00:00
|
|
|
const {interfaces: Ci, utils: Cu} = Components;
|
2015-03-17 14:27:20 +00:00
|
|
|
|
|
|
|
const errors = [
|
2015-04-15 11:18:00 +00:00
|
|
|
"ElementNotAccessibleError",
|
2015-03-17 14:27:20 +00:00
|
|
|
"ElementNotVisibleError",
|
2015-04-17 17:43:05 +00:00
|
|
|
"InvalidArgumentError",
|
2015-03-31 17:00:32 +00:00
|
|
|
"InvalidElementStateError",
|
2015-04-15 11:18:00 +00:00
|
|
|
"InvalidSelectorError",
|
2015-04-15 12:38:01 +00:00
|
|
|
"InvalidSessionIdError",
|
2015-03-17 14:27:20 +00:00
|
|
|
"JavaScriptError",
|
|
|
|
"NoAlertOpenError",
|
|
|
|
"NoSuchElementError",
|
|
|
|
"NoSuchFrameError",
|
|
|
|
"NoSuchWindowError",
|
|
|
|
"ScriptTimeoutError",
|
|
|
|
"SessionNotCreatedError",
|
2015-04-15 11:18:00 +00:00
|
|
|
"StaleElementReferenceError",
|
2015-03-17 14:27:20 +00:00
|
|
|
"TimeoutError",
|
2015-04-20 12:53:51 +00:00
|
|
|
"UnableToSetCookieError",
|
2015-03-17 14:27:20 +00:00
|
|
|
"UnknownCommandError",
|
|
|
|
"UnknownError",
|
|
|
|
"UnsupportedOperationError",
|
|
|
|
"WebDriverError",
|
|
|
|
];
|
|
|
|
|
|
|
|
this.EXPORTED_SYMBOLS = ["error"].concat(errors);
|
|
|
|
|
|
|
|
this.error = {};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Checks if obj is an instance of the Error prototype in a safe manner.
|
|
|
|
* Prefer using this over using instanceof since the Error prototype
|
2016-01-07 14:37:06 +00:00
|
|
|
* isn't unique across browsers, and XPCOM nsIException's are special
|
2015-03-17 14:27:20 +00:00
|
|
|
* snowflakes.
|
2015-04-15 11:18:00 +00:00
|
|
|
*
|
|
|
|
* @param {*} val
|
|
|
|
* Any value that should be undergo the test for errorness.
|
|
|
|
* @return {boolean}
|
|
|
|
* True if error, false otherwise.
|
2015-03-17 14:27:20 +00:00
|
|
|
*/
|
2015-04-15 11:18:00 +00:00
|
|
|
error.isError = function(val) {
|
|
|
|
if (val === null || typeof val != "object") {
|
2015-03-17 14:27:20 +00:00
|
|
|
return false;
|
2016-01-07 14:37:06 +00:00
|
|
|
} else if (val instanceof Ci.nsIException) {
|
2015-03-17 14:27:20 +00:00
|
|
|
return true;
|
|
|
|
} else {
|
2015-04-08 18:02:34 +00:00
|
|
|
return Object.getPrototypeOf(val) == "Error";
|
2015-03-17 14:27:20 +00:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Checks if obj is an object in the WebDriverError prototypal chain.
|
|
|
|
*/
|
|
|
|
error.isWebDriverError = function(obj) {
|
|
|
|
return error.isError(obj) &&
|
Bug 1153822: Adjust Marionette responses to match WebDriver protocol
Introduce protocol version levels in the Marionette server.
On establishing a connection to a local end, the remote will return a
`marionetteProtocol` field indicating which level it speaks.
The protocol level can be used by local ends to either fall into
compatibility mode or warn the user that the local end is incompatible
with the remote.
The protocol is currently also more expressive than it needs to be and
this expressiveness has previously resulted in subtle inconsistencies
in the fields returned.
This patch reduces the amount of superfluous fields, reducing the
amount of data sent. Aligning the protocol closer to the WebDriver
specification's expectations will also reduce the amount of
post-processing required in the httpd.
Previous to this patch, this is a value response:
{"from":"0","value":null,"status":0,"sessionId":"{6b6d68d2-4ac9-4308-9f07-d2e72519c407}"}
And this for ok responses:
{"from":"0","ok":true}
And this for errors:
{"from":"0","status":21,"sessionId":"{6b6d68d2-4ac9-4308-9f07-d2e72519c407}","error":{"message":"Error loading page, timed out (onDOMContentLoaded)","stacktrace":null,"status":21}}
This patch drops the `from` and `sessionId` fields, and the `status`
field from non-error responses. It also drops the `ok` field in non-value
responses and flattens the error response to a simple dictionary with the
`error` (previously `status`), `message`, and `stacktrace` properties,
which are now all required.
r=jgriffin
--HG--
extra : commitid : FbEkv70rxl9
extra : rebase_source : 3116110a0d197289cc95eba8748be0a33566c5a5
2015-05-21 10:26:58 +00:00
|
|
|
("name" in obj && errors.indexOf(obj.name) >= 0);
|
2015-03-17 14:27:20 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 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>";
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2015-09-26 16:12:01 +00:00
|
|
|
/**
|
2016-01-29 12:57:46 +00:00
|
|
|
* Marshal a WebDriverError prototype to a JSON dictionary.
|
2015-09-26 16:12:01 +00:00
|
|
|
*
|
2016-01-29 12:57:46 +00:00
|
|
|
* @param {WebDriverError} err
|
|
|
|
* Error to serialise.
|
2015-09-26 16:12:01 +00:00
|
|
|
*
|
|
|
|
* @return {Object.<string, Object>}
|
2016-01-29 12:57:46 +00:00
|
|
|
* JSON dictionary with the keys "error", "message", and "stacktrace".
|
|
|
|
* @throws {TypeError}
|
|
|
|
* If error type is not serialisable.
|
2015-09-26 16:12:01 +00:00
|
|
|
*/
|
|
|
|
error.toJson = function(err) {
|
2016-01-29 12:57:46 +00:00
|
|
|
if (!error.isWebDriverError(err)) {
|
|
|
|
throw new TypeError(`Unserialisable error type: ${err}`);
|
|
|
|
}
|
|
|
|
|
2015-09-26 16:12:01 +00:00
|
|
|
let json = {
|
|
|
|
error: err.status,
|
|
|
|
message: err.message || null,
|
|
|
|
stacktrace: err.stack || null,
|
|
|
|
};
|
|
|
|
return json;
|
|
|
|
};
|
|
|
|
|
2016-01-29 12:57:46 +00:00
|
|
|
/**
|
|
|
|
* Unmarshal a JSON dictionary to a WebDriverError prototype.
|
|
|
|
*
|
|
|
|
* @param {Object.<string, string>} json
|
|
|
|
* JSON dictionary with the keys "error", "message", and "stacktrace".
|
|
|
|
*
|
|
|
|
* @return {WebDriverError}
|
|
|
|
* Deserialised error prototype.
|
|
|
|
*/
|
|
|
|
error.fromJson = function(json) {
|
|
|
|
if (!statusLookup.has(json.error)) {
|
|
|
|
throw new TypeError(`Undeserialisable error type: ${json.error}`);
|
|
|
|
}
|
|
|
|
|
|
|
|
let errCls = statusLookup.get(json.error);
|
|
|
|
let err = new errCls(json.message);
|
|
|
|
if ("stacktrace" in json) {
|
|
|
|
err.stack = json.stacktrace;
|
|
|
|
}
|
|
|
|
return err;
|
|
|
|
};
|
|
|
|
|
2015-03-17 14:27:20 +00:00
|
|
|
/**
|
|
|
|
* 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;
|
2015-04-02 19:07:20 +00:00
|
|
|
this.status = "webdriver error";
|
2015-03-17 14:27:20 +00:00
|
|
|
};
|
|
|
|
WebDriverError.prototype = Object.create(Error.prototype);
|
|
|
|
|
2015-04-15 11:18:00 +00:00
|
|
|
this.ElementNotAccessibleError = function(msg) {
|
|
|
|
WebDriverError.call(this, msg);
|
|
|
|
this.name = "ElementNotAccessibleError";
|
|
|
|
this.status = "element not accessible";
|
|
|
|
};
|
|
|
|
ElementNotAccessibleError.prototype = Object.create(WebDriverError.prototype);
|
|
|
|
|
2015-03-17 14:27:20 +00:00
|
|
|
this.ElementNotVisibleError = function(msg) {
|
|
|
|
WebDriverError.call(this, msg);
|
|
|
|
this.name = "ElementNotVisibleError";
|
|
|
|
this.status = "element not visible";
|
|
|
|
};
|
|
|
|
ElementNotVisibleError.prototype = Object.create(WebDriverError.prototype);
|
2015-03-31 17:00:32 +00:00
|
|
|
|
2015-04-17 17:43:05 +00:00
|
|
|
this.InvalidArgumentError = function(msg) {
|
2015-04-02 14:16:00 +00:00
|
|
|
WebDriverError.call(this, msg);
|
2015-04-17 17:43:05 +00:00
|
|
|
this.name = "InvalidArgumentError";
|
|
|
|
this.status = "invalid argument";
|
2015-04-02 14:16:00 +00:00
|
|
|
};
|
2015-04-17 17:43:05 +00:00
|
|
|
InvalidArgumentError.prototype = Object.create(WebDriverError.prototype);
|
2015-04-02 14:16:00 +00:00
|
|
|
|
2015-03-31 17:00:32 +00:00
|
|
|
this.InvalidElementStateError = function(msg) {
|
2015-03-17 14:27:20 +00:00
|
|
|
WebDriverError.call(this, msg);
|
2015-03-31 17:00:32 +00:00
|
|
|
this.name = "InvalidElementStateError";
|
|
|
|
this.status = "invalid element state";
|
2015-03-17 14:27:20 +00:00
|
|
|
};
|
2015-03-31 17:00:32 +00:00
|
|
|
InvalidElementStateError.prototype = Object.create(WebDriverError.prototype);
|
2015-03-17 14:27:20 +00:00
|
|
|
|
2015-04-15 11:18:00 +00:00
|
|
|
this.InvalidSelectorError = function(msg) {
|
|
|
|
WebDriverError.call(this, msg);
|
|
|
|
this.name = "InvalidSelectorError";
|
|
|
|
this.status = "invalid selector";
|
|
|
|
};
|
|
|
|
InvalidSelectorError.prototype = Object.create(WebDriverError.prototype);
|
|
|
|
|
2015-04-15 12:38:01 +00:00
|
|
|
this.InvalidSessionIdError = function(msg) {
|
|
|
|
WebDriverError.call(this, msg);
|
|
|
|
this.name = "InvalidSessionIdError";
|
|
|
|
this.status = "invalid session id";
|
|
|
|
};
|
|
|
|
InvalidSessionIdError.prototype = Object.create(WebDriverError.prototype);
|
|
|
|
|
2015-03-17 14:27:20 +00:00
|
|
|
/**
|
|
|
|
* 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.
|
2016-01-18 18:55:52 +00:00
|
|
|
* @param {string=} fnName
|
2015-03-17 14:27:20 +00:00
|
|
|
* The name of the function to use in the stack trace message
|
|
|
|
* (e.g. execute_script).
|
2016-01-18 18:55:52 +00:00
|
|
|
* @param {string=} file
|
2015-03-17 14:27:20 +00:00
|
|
|
* The filename of the test file containing the Marionette
|
|
|
|
* command that caused this error to occur.
|
2016-01-18 18:55:52 +00:00
|
|
|
* @param {number=} line
|
2015-03-17 14:27:20 +00:00
|
|
|
* The line number of the above test file.
|
|
|
|
* @param {string=} script
|
|
|
|
* The JS script being executed in text form.
|
|
|
|
*/
|
2016-01-18 18:55:52 +00:00
|
|
|
this.JavaScriptError = function(
|
|
|
|
err, fnName = null, file = null, line = null, script = null) {
|
2015-03-17 14:27:20 +00:00
|
|
|
let msg = String(err);
|
|
|
|
let trace = "";
|
|
|
|
|
2016-01-18 18:55:52 +00:00
|
|
|
if (fnName) {
|
|
|
|
trace += fnName;
|
|
|
|
if (file) {
|
|
|
|
trace += ` @${file}`;
|
|
|
|
if (line) {
|
|
|
|
trace += `, line ${line}`;
|
|
|
|
}
|
2015-03-17 14:27:20 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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 + "\"";
|
|
|
|
}
|
2015-09-29 10:02:48 +00:00
|
|
|
trace += "\nStack:\n" + String(err.stack);
|
2015-03-17 14:27:20 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
WebDriverError.call(this, msg);
|
|
|
|
this.name = "JavaScriptError";
|
|
|
|
this.status = "javascript error";
|
|
|
|
this.stack = trace;
|
|
|
|
};
|
|
|
|
JavaScriptError.prototype = Object.create(WebDriverError.prototype);
|
|
|
|
|
2015-03-31 17:00:32 +00:00
|
|
|
this.NoAlertOpenError = function(msg) {
|
2015-03-17 14:27:20 +00:00
|
|
|
WebDriverError.call(this, msg);
|
2015-03-31 17:00:32 +00:00
|
|
|
this.name = "NoAlertOpenError";
|
|
|
|
this.status = "no such alert";
|
2015-09-26 16:12:01 +00:00
|
|
|
};
|
2015-03-31 17:00:32 +00:00
|
|
|
NoAlertOpenError.prototype = Object.create(WebDriverError.prototype);
|
|
|
|
|
|
|
|
this.NoSuchElementError = function(msg) {
|
|
|
|
WebDriverError.call(this, msg);
|
|
|
|
this.name = "NoSuchElementError";
|
|
|
|
this.status = "no such element";
|
2015-03-17 14:27:20 +00:00
|
|
|
};
|
2015-03-31 17:00:32 +00:00
|
|
|
NoSuchElementError.prototype = Object.create(WebDriverError.prototype);
|
|
|
|
|
|
|
|
this.NoSuchFrameError = function(msg) {
|
|
|
|
WebDriverError.call(this, msg);
|
|
|
|
this.name = "NoSuchFrameError";
|
|
|
|
this.status = "no such frame";
|
|
|
|
};
|
|
|
|
NoSuchFrameError.prototype = Object.create(WebDriverError.prototype);
|
2015-03-17 14:27:20 +00:00
|
|
|
|
|
|
|
this.NoSuchWindowError = function(msg) {
|
|
|
|
WebDriverError.call(this, msg);
|
|
|
|
this.name = "NoSuchWindowError";
|
|
|
|
this.status = "no such window";
|
|
|
|
};
|
|
|
|
NoSuchWindowError.prototype = Object.create(WebDriverError.prototype);
|
|
|
|
|
|
|
|
this.ScriptTimeoutError = function(msg) {
|
|
|
|
WebDriverError.call(this, msg);
|
|
|
|
this.name = "ScriptTimeoutError";
|
|
|
|
this.status = "script timeout";
|
|
|
|
};
|
|
|
|
ScriptTimeoutError.prototype = Object.create(WebDriverError.prototype);
|
|
|
|
|
|
|
|
this.SessionNotCreatedError = function(msg) {
|
|
|
|
WebDriverError.call(this, msg);
|
|
|
|
this.name = "SessionNotCreatedError";
|
|
|
|
this.status = "session not created";
|
2015-04-15 11:18:00 +00:00
|
|
|
};
|
2015-03-17 14:27:20 +00:00
|
|
|
SessionNotCreatedError.prototype = Object.create(WebDriverError.prototype);
|
|
|
|
|
2015-04-15 11:18:00 +00:00
|
|
|
this.StaleElementReferenceError = function(msg) {
|
|
|
|
WebDriverError.call(this, msg);
|
|
|
|
this.name = "StaleElementReferenceError";
|
|
|
|
this.status = "stale element reference";
|
|
|
|
};
|
|
|
|
StaleElementReferenceError.prototype = Object.create(WebDriverError.prototype);
|
|
|
|
|
2015-03-31 17:00:32 +00:00
|
|
|
this.TimeoutError = function(msg) {
|
|
|
|
WebDriverError.call(this, msg);
|
|
|
|
this.name = "TimeoutError";
|
|
|
|
this.status = "timeout";
|
2015-03-17 14:27:20 +00:00
|
|
|
};
|
2015-03-31 17:00:32 +00:00
|
|
|
TimeoutError.prototype = Object.create(WebDriverError.prototype);
|
2015-03-17 14:27:20 +00:00
|
|
|
|
2015-04-20 12:53:51 +00:00
|
|
|
this.UnableToSetCookieError = function(msg) {
|
|
|
|
WebDriverError.call(this, msg);
|
|
|
|
this.name = "UnableToSetCookieError";
|
|
|
|
this.status = "unable to set cookie";
|
|
|
|
};
|
|
|
|
UnableToSetCookieError.prototype = Object.create(WebDriverError.prototype);
|
|
|
|
|
2015-03-31 17:00:32 +00:00
|
|
|
this.UnknownCommandError = function(msg) {
|
|
|
|
WebDriverError.call(this, msg);
|
|
|
|
this.name = "UnknownCommandError";
|
|
|
|
this.status = "unknown command";
|
2015-03-17 14:27:20 +00:00
|
|
|
};
|
2015-03-31 17:00:32 +00:00
|
|
|
UnknownCommandError.prototype = Object.create(WebDriverError.prototype);
|
|
|
|
|
|
|
|
this.UnknownError = function(msg) {
|
|
|
|
WebDriverError.call(this, msg);
|
|
|
|
this.name = "UnknownError";
|
|
|
|
this.status = "unknown error";
|
|
|
|
};
|
|
|
|
UnknownError.prototype = Object.create(WebDriverError.prototype);
|
2015-03-17 14:27:20 +00:00
|
|
|
|
|
|
|
this.UnsupportedOperationError = function(msg) {
|
|
|
|
WebDriverError.call(this, msg);
|
|
|
|
this.name = "UnsupportedOperationError";
|
|
|
|
this.status = "unsupported operation";
|
|
|
|
};
|
|
|
|
UnsupportedOperationError.prototype = Object.create(WebDriverError.prototype);
|
2016-01-29 12:57:46 +00:00
|
|
|
|
|
|
|
const nameLookup = new Map();
|
|
|
|
const statusLookup = new Map();
|
|
|
|
for (let s of errors) {
|
|
|
|
let cls = this[s];
|
|
|
|
let inst = new cls();
|
|
|
|
nameLookup.set(inst.name, cls);
|
|
|
|
statusLookup.set(inst.status, cls);
|
|
|
|
};
|