mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-25 19:25:43 +00:00
1d15796afc
In order to achieve WebDriver parity, Marionette needs the ability to evaluate scripts in content space with lasting side-effects. This means that state modifications should affect behaviour and state of the browsing context, and such transgress the boundaries of the sandbox. This patch brings a new script evaluation module that is shared between code in chrome- and content space. This brings the number of unique script evaluation implementations in Marionette down from six to one. evaluate.sandbox provides the main entry-point for execution. It is compatible with existing Marionette uses of Execute Script and Execute Async Script commands in Mozilla clients, but also provides a new stateful sandbox for evaluation that should have lasting side-effects. It is not expected that Mozilla clients, such as testing/marionette/client and the Node.js client in Gaia, should have to change as a consequence of this change. A substantial change to the script's runtime environment is that many globals that previously existed are now only exposed whenever needed. This means for example that Simple Test harness functionality (waitFor, ok, isnot, is, &c.) is only available when using a sandbox augmented with a Simple Test harness adapter. Conversely, this patch does not expose marionetteScriptFinished as a callback to asynchronous scripts for sandboxes which sandboxName parameter is undefined, because this is what determines if the script should be evaluated under WebDriver conformance constraints. In all other cases where sandboxName _is_ defined, the traditional marionetteScriptFinished et al. runtime environment is preserved. MozReview-Commit-ID: 8FZ6rNVImuC
209 lines
5.9 KiB
JavaScript
209 lines
5.9 KiB
JavaScript
/* 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;
|
|
|
|
Cu.import("chrome://marionette/content/error.js");
|
|
|
|
this.EXPORTED_SYMBOLS = ["simpletest"];
|
|
|
|
this.simpletest = {};
|
|
|
|
/**
|
|
* The simpletest harness, exposed in the script evaluation sandbox.
|
|
*/
|
|
simpletest.Harness = class {
|
|
constructor(window, context, contentLogger, timeout, testName) {
|
|
this.window = window;
|
|
this.tests = [];
|
|
this.logger = contentLogger;
|
|
this.context = context;
|
|
this.timeout = timeout;
|
|
this.testName = testName;
|
|
this.TEST_UNEXPECTED_FAIL = "TEST-UNEXPECTED-FAIL";
|
|
this.TEST_UNEXPECTED_PASS = "TEST-UNEXPECTED-PASS";
|
|
this.TEST_PASS = "TEST-PASS";
|
|
this.TEST_KNOWN_FAIL = "TEST-KNOWN-FAIL";
|
|
}
|
|
|
|
get exports() {
|
|
return new Map([
|
|
["ok", this.ok.bind(this)],
|
|
["is", this.is.bind(this)],
|
|
["isnot", this.isnot.bind(this)],
|
|
["todo", this.todo.bind(this)],
|
|
["log", this.log.bind(this)],
|
|
["getLogs", this.getLogs.bind(this)],
|
|
["generate_results", this.generate_results.bind(this)],
|
|
["waitFor", this.waitFor.bind(this)],
|
|
["TEST_PASS", this.TEST_PASS],
|
|
["TEST_KNOWN_FAIL", this.TEST_KNOWN_FAIL],
|
|
["TEST_UNEXPECTED_FAIL", this.TEST_UNEXPECTED_FAIL],
|
|
["TEST_UNEXPECTED_PASS", this.TEST_UNEXPECTED_PASS],
|
|
]);
|
|
}
|
|
|
|
addTest(condition, name, passString, failString, diag, state) {
|
|
let test = {
|
|
result: !!condition,
|
|
name: name,
|
|
diag: diag,
|
|
state: state
|
|
};
|
|
this.logResult(
|
|
test,
|
|
typeof passString == "undefined" ? this.TEST_PASS : passString,
|
|
typeof failString == "undefined" ? this.TEST_UNEXPECTED_FAIL : failString);
|
|
this.tests.push(test);
|
|
}
|
|
|
|
ok(condition, name, passString, failString) {
|
|
let diag = `${this.repr(condition)} was ${!!condition}, expected true`;
|
|
this.addTest(condition, name, passString, failString, diag);
|
|
}
|
|
|
|
is(a, b, name, passString, failString) {
|
|
let pass = (a == b);
|
|
let diag = pass ? this.repr(a) + " should equal " + this.repr(b)
|
|
: "got " + this.repr(a) + ", expected " + this.repr(b);
|
|
this.addTest(pass, name, passString, failString, diag);
|
|
}
|
|
|
|
isnot(a, b, name, passString, failString) {
|
|
let pass = (a != b);
|
|
let diag = pass ? this.repr(a) + " should not equal " + this.repr(b)
|
|
: "didn't expect " + this.repr(a) + ", but got it";
|
|
this.addTest(pass, name, passString, failString, diag);
|
|
}
|
|
|
|
todo(condition, name, passString, failString) {
|
|
let diag = this.repr(condition) + " was expected false";
|
|
this.addTest(!condition,
|
|
name,
|
|
typeof(passString) == "undefined" ? this.TEST_KNOWN_FAIL : passString,
|
|
typeof(failString) == "undefined" ? this.TEST_UNEXPECTED_FAIL : failString,
|
|
diag,
|
|
"todo");
|
|
}
|
|
|
|
log(msg, level) {
|
|
dump("MARIONETTE LOG: " + (level ? level : "INFO") + ": " + msg + "\n");
|
|
if (this.logger) {
|
|
this.logger.log(msg, level);
|
|
}
|
|
}
|
|
|
|
// TODO(ato): Suspect this isn't used anywhere
|
|
getLogs() {
|
|
if (this.logger) {
|
|
return this.logger.get();
|
|
}
|
|
}
|
|
|
|
generate_results() {
|
|
let passed = 0;
|
|
let failures = [];
|
|
let expectedFailures = [];
|
|
let unexpectedSuccesses = [];
|
|
for (let i in this.tests) {
|
|
let isTodo = (this.tests[i].state == "todo");
|
|
if(this.tests[i].result) {
|
|
if (isTodo) {
|
|
expectedFailures.push({'name': this.tests[i].name, 'diag': this.tests[i].diag});
|
|
}
|
|
else {
|
|
passed++;
|
|
}
|
|
}
|
|
else {
|
|
if (isTodo) {
|
|
unexpectedSuccesses.push({'name': this.tests[i].name, 'diag': this.tests[i].diag});
|
|
}
|
|
else {
|
|
failures.push({'name': this.tests[i].name, 'diag': this.tests[i].diag});
|
|
}
|
|
}
|
|
}
|
|
// Reset state in case this object is reused for more tests.
|
|
this.tests = [];
|
|
return {
|
|
passed: passed,
|
|
failures: failures,
|
|
expectedFailures: expectedFailures,
|
|
unexpectedSuccesses: unexpectedSuccesses,
|
|
};
|
|
}
|
|
|
|
logToFile(file) {
|
|
//TODO
|
|
}
|
|
|
|
logResult(test, passString, failString) {
|
|
//TODO: dump to file
|
|
let resultString = test.result ? passString : failString;
|
|
let diagnostic = test.name + (test.diag ? " - " + test.diag : "");
|
|
let msg = resultString + " | " + this.testName + " | " + diagnostic;
|
|
dump("MARIONETTE TEST RESULT:" + msg + "\n");
|
|
}
|
|
|
|
repr(o) {
|
|
if (typeof o == "undefined") {
|
|
return "undefined";
|
|
} else if (o === null) {
|
|
return "null";
|
|
}
|
|
|
|
try {
|
|
if (typeof o.__repr__ == "function") {
|
|
return o.__repr__();
|
|
} else if (typeof o.repr == "function" && o.repr !== arguments.callee) {
|
|
return o.repr();
|
|
}
|
|
} catch (e) {}
|
|
|
|
try {
|
|
if (typeof o.NAME === "string" &&
|
|
(o.toString === Function.prototype.toString || o.toString === Object.prototype.toString)) {
|
|
return o.NAME;
|
|
}
|
|
} catch (e) {}
|
|
|
|
let ostring;
|
|
try {
|
|
ostring = (o + "");
|
|
} catch (e) {
|
|
return "[" + typeof(o) + "]";
|
|
}
|
|
|
|
if (typeof o == "function") {
|
|
o = ostring.replace(/^\s+/, "");
|
|
let idx = o.indexOf("{");
|
|
if (idx != -1) {
|
|
o = o.substr(0, idx) + "{...}";
|
|
}
|
|
}
|
|
return ostring;
|
|
}
|
|
|
|
waitFor(callback, test, timeout) {
|
|
if (test()) {
|
|
callback();
|
|
return;
|
|
}
|
|
|
|
let now = new Date();
|
|
let deadline = (timeout instanceof Date) ? timeout :
|
|
new Date(now.valueOf() + (typeof timeout == "undefined" ? this.timeout : timeout));
|
|
if (deadline <= now) {
|
|
dump("waitFor timeout: " + test.toString() + "\n");
|
|
// the script will timeout here, so no need to raise a separate
|
|
// timeout exception
|
|
return;
|
|
}
|
|
this.window.setTimeout(this.waitFor.bind(this), 100, callback, test, deadline);
|
|
}
|
|
};
|