Bug 1950639 - Implement browser.test changes required for WPT r=robwu
Differential Revision: https://phabricator.services.mozilla.com/D244091
This commit is contained in:
@@ -19,6 +19,10 @@ user_pref("dom.testing.testutils.enabled", true);
|
||||
user_pref("extensions.autoDisableScopes", 10);
|
||||
// Don't open a dialog to show available add-on updates
|
||||
user_pref("extensions.update.notifyUser", false);
|
||||
// For cross-browser tests, don't fail on manifest warnings like unknown keys.
|
||||
user_pref("extensions.webextensions.warnings-as-errors", false);
|
||||
// Adjust behavior of browser.test API to be compatible across engines.
|
||||
user_pref("extensions.wpt.enabled", true);
|
||||
// Enable test mode to run multiple tests in parallel
|
||||
user_pref("focusmanager.testmode", true);
|
||||
// Enable fake media streams for getUserMedia
|
||||
|
||||
@@ -1261,6 +1261,9 @@ export class SpecialPowersParent extends JSWindowActorParent {
|
||||
extension.on("test-eq", resultListener);
|
||||
extension.on("test-log", resultListener);
|
||||
extension.on("test-done", resultListener);
|
||||
// Web Platform Test subtest started and finished events.
|
||||
extension.on("test-task-start", resultListener);
|
||||
extension.on("test-task-done", resultListener);
|
||||
|
||||
extension.on("test-message", messageListener);
|
||||
|
||||
|
||||
@@ -24,6 +24,10 @@ const lazy = XPCOMUtils.declareLazy({
|
||||
service: "@mozilla.org/content/style-sheet-service;1",
|
||||
iid: Ci.nsIStyleSheetService,
|
||||
},
|
||||
wptEnabled: {
|
||||
pref: "extensions.wpt.enabled",
|
||||
default: false,
|
||||
},
|
||||
});
|
||||
|
||||
const ScriptError = Components.Constructor(
|
||||
@@ -3150,4 +3154,10 @@ export var ExtensionCommon = {
|
||||
|
||||
MultiAPIManager,
|
||||
LazyAPIManager,
|
||||
|
||||
// Whether we're running under Web Platform Tests mode,
|
||||
// required to adjust some cross-browser behaviors.
|
||||
get isInWPT() {
|
||||
return Cu.isInAutomation && lazy.wptEnabled;
|
||||
},
|
||||
};
|
||||
|
||||
@@ -2476,6 +2476,13 @@ class ArrayType extends Type {
|
||||
}
|
||||
value = v.value;
|
||||
|
||||
// eslint-disable-next-line no-use-before-define
|
||||
if (value && this.itemType instanceof FunctionType) {
|
||||
// This needs special handling if we're expecting an array of functions,
|
||||
// because iterating over (wrapped) callable items fails otherwise.
|
||||
value = this.extractItems(value, context);
|
||||
}
|
||||
|
||||
let result = [];
|
||||
for (let [i, element] of value.entries()) {
|
||||
element = context.withPath(String(i), () =>
|
||||
@@ -2509,6 +2516,29 @@ class ArrayType extends Type {
|
||||
return this.postprocess({ value: result }, context);
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts all items of the given array, including callable
|
||||
* ones which would normally be omitted by X-ray wrappers.
|
||||
*
|
||||
* @see ObjectType.extractProperties for more details.
|
||||
*
|
||||
* @param {Array} value
|
||||
* @param {Context} context
|
||||
* @returns {Array}
|
||||
*/
|
||||
extractItems(value, context) {
|
||||
let klass = ChromeUtils.getClassName(value, true);
|
||||
if (klass !== "Array") {
|
||||
throw context.error(
|
||||
`Expected a plain JavaScript array, got a ${klass}`,
|
||||
`be a plain JavaScript array`
|
||||
);
|
||||
}
|
||||
let obj = ChromeUtils.shallowClone(value);
|
||||
obj.length = value.length;
|
||||
return Array.from(obj);
|
||||
}
|
||||
|
||||
checkBaseType(baseType) {
|
||||
return baseType == "array";
|
||||
}
|
||||
|
||||
@@ -131,7 +131,53 @@ const toSource = value => {
|
||||
|
||||
this.test = class extends ExtensionAPI {
|
||||
getAPI(context) {
|
||||
const CONTEXT_DESTROYED = "Test context destroyed.";
|
||||
const { extension } = context;
|
||||
let running = false;
|
||||
let testTasks = [];
|
||||
let unnamed = 0;
|
||||
|
||||
async function runTasks(tests) {
|
||||
testTasks.push(...tests);
|
||||
|
||||
// If still running tasks from a previous call, queue new ones and bail.
|
||||
if (running) {
|
||||
return;
|
||||
}
|
||||
|
||||
let onClosed = Promise.withResolvers();
|
||||
onClosed.close = () => onClosed.reject(CONTEXT_DESTROYED);
|
||||
context.callOnClose(onClosed);
|
||||
|
||||
try {
|
||||
running = true;
|
||||
while (testTasks.length) {
|
||||
let task = testTasks.shift();
|
||||
let name = task.name || `unnamed_test_${++unnamed}`;
|
||||
let stack = getStack(context.getCaller());
|
||||
extension.emit("test-task-start", name, stack);
|
||||
try {
|
||||
await Promise.race([task(), onClosed.promise]);
|
||||
|
||||
if (!context.active) {
|
||||
assertTrue(false, CONTEXT_DESTROYED);
|
||||
throw new ExtensionUtils.ExtensionError(CONTEXT_DESTROYED);
|
||||
}
|
||||
} catch (e) {
|
||||
let err = `Exception running ${name}: ${e.message}`;
|
||||
assertTrue(false, err);
|
||||
Cu.reportError(err);
|
||||
throw e;
|
||||
} finally {
|
||||
extension.emit("test-task-done", testTasks.length, name, stack);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
context.forgetOnClose(onClosed);
|
||||
testTasks.length = 0;
|
||||
running = false;
|
||||
}
|
||||
}
|
||||
|
||||
function getStack(savedFrame = null) {
|
||||
if (savedFrame) {
|
||||
@@ -327,6 +373,15 @@ this.test = class extends ExtensionAPI {
|
||||
},
|
||||
|
||||
assertThrows(func, expectedError, msg) {
|
||||
if (!expectedError) {
|
||||
if (ExtensionCommon.isInWPT) {
|
||||
expectedError = /.*/;
|
||||
} else {
|
||||
throw new ExtensionUtils.ExtensionError(
|
||||
"Missing required expectedError"
|
||||
);
|
||||
}
|
||||
}
|
||||
try {
|
||||
func();
|
||||
|
||||
@@ -351,6 +406,10 @@ this.test = class extends ExtensionAPI {
|
||||
}
|
||||
},
|
||||
|
||||
runTests(tests) {
|
||||
return runTasks(tests);
|
||||
},
|
||||
|
||||
onMessage: new TestEventManager({
|
||||
context,
|
||||
name: "test.onMessage",
|
||||
|
||||
@@ -137,6 +137,7 @@
|
||||
{
|
||||
"name": "assertThrows",
|
||||
"type": "function",
|
||||
"allowAmbiguousOptionalArguments": true,
|
||||
"parameters": [
|
||||
{
|
||||
"name": "func",
|
||||
@@ -144,7 +145,9 @@
|
||||
},
|
||||
{
|
||||
"name": "expectedError",
|
||||
"$ref": "ExpectedError"
|
||||
"$ref": "ExpectedError",
|
||||
"optional": true,
|
||||
"description": "Required, unless running with extensions.wpt.enabled"
|
||||
},
|
||||
{
|
||||
"name": "message",
|
||||
@@ -152,6 +155,21 @@
|
||||
"optional": true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "runTests",
|
||||
"type": "function",
|
||||
"async": true,
|
||||
"parameters": [
|
||||
{
|
||||
"name": "tests",
|
||||
"type": "array",
|
||||
"minItems": 1,
|
||||
"items": {
|
||||
"type": "function"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"types": [
|
||||
|
||||
@@ -32,3 +32,4 @@ skip-if = ["true"] # Bug 1748318 - Add WebIDL bindings for `tabs`
|
||||
skip-if = ["true"] # Bug 1748318 - Add WebIDL bindings for `tabs`
|
||||
|
||||
["test_ext_test.html"]
|
||||
skip-if = ["true"] # Bug 1950639 - Fix webidl version once WPT design stabilizes.
|
||||
|
||||
@@ -52,7 +52,7 @@ function loadExtensionAndInterceptTest(extensionData) {
|
||||
//
|
||||
// All browser.test calls results are intercepted by the test itself, see verifyTestResults for
|
||||
// the expectations of each browser.test call.
|
||||
function testScript() {
|
||||
async function testScript() {
|
||||
browser.test.notifyPass("dot notifyPass");
|
||||
browser.test.notifyFail("dot notifyFail");
|
||||
browser.test.log("dot log");
|
||||
@@ -124,6 +124,13 @@ function testScript() {
|
||||
/dummy/
|
||||
);
|
||||
|
||||
browser.test.assertThrows(
|
||||
() => { throw new Error("dummy4"); },
|
||||
"dummy4"
|
||||
// allowAmbiguousOptionalArguments: with two optional arguments,
|
||||
// passing a string is interpreted as `expectedError`, not `message`.
|
||||
);
|
||||
|
||||
// The WebIDL version of assertDeepEq structurally clones before sending the
|
||||
// params to the main thread. This check verifies that the behavior is
|
||||
// consistent between the WebIDL and Schemas.sys.mjs-generated API bindings.
|
||||
@@ -156,11 +163,38 @@ function testScript() {
|
||||
browser.test.assertEq(true, false, document.createElement("div"));
|
||||
}
|
||||
|
||||
await browser.test.runTests([
|
||||
async () => {
|
||||
await new Promise(r => setTimeout(r, 100));
|
||||
browser.test.assertTrue(true, "After await.");
|
||||
},
|
||||
function subTest2() {
|
||||
// Normally, assertThrows() requires the second `expectedError` argument,
|
||||
// so the inner assertThrows() will throw, producing one success result.
|
||||
|
||||
// In WPT mode, the inner assertThrows() will not throw,
|
||||
// producing two failure results.
|
||||
browser.test.assertThrows(
|
||||
() => browser.test.assertThrows(() => "no-throw"),
|
||||
/Missing required expectedError/
|
||||
);
|
||||
|
||||
throw new Error("Sub2");
|
||||
},
|
||||
() => {
|
||||
// This task will not run because previous task throws.
|
||||
browser.test.assertTrue(false, "Never runs.");
|
||||
},
|
||||
]).catch(e => {
|
||||
// We could use assertRejects, but the flow would be harder to follow.
|
||||
browser.test.assertEq(e.message, "Sub2", "Expected exception.");
|
||||
});
|
||||
|
||||
browser.test.sendMessage("Ran test at", location.protocol);
|
||||
browser.test.sendMessage("This is the last browser.test call");
|
||||
}
|
||||
|
||||
function verifyTestResults(results, shortName, expectedProtocol, useServiceWorker) {
|
||||
function verifyTestResults(results, shortName, expectedProtocol, useServiceWorker, wptMode) {
|
||||
let expectations = [
|
||||
["test-done", true, "dot notifyPass"],
|
||||
["test-done", false, "dot notifyFail"],
|
||||
@@ -236,6 +270,10 @@ function verifyTestResults(results, shortName, expectedProtocol, useServiceWorke
|
||||
"test-result", false,
|
||||
"Function did not throw, expected error '/dummy/'"
|
||||
],
|
||||
[
|
||||
"test-result", true,
|
||||
"Function threw, expecting error to match '\"dummy4\"', got 'Error: dummy4'"
|
||||
],
|
||||
[
|
||||
"test-result", true,
|
||||
"Function threw, expecting error to match '/An unexpected error occurred/', got 'Error: An unexpected error occurred': assertDeepEq obj with function throws",
|
||||
@@ -262,6 +300,33 @@ function verifyTestResults(results, shortName, expectedProtocol, useServiceWorke
|
||||
]);
|
||||
}
|
||||
|
||||
expectations.push(
|
||||
["test-task-start", "unnamed_test_1"],
|
||||
["test-result", true, "After await."],
|
||||
["test-task-done", 2, "unnamed_test_1"],
|
||||
);
|
||||
|
||||
if (wptMode) {
|
||||
expectations.push(
|
||||
["test-task-start", "subTest2"],
|
||||
["test-result", false, "Function did not throw, expected error '/.*/'"],
|
||||
["test-result", false, "Function did not throw, expected error '/Missing required expectedError/'"],
|
||||
["test-result", false, "Exception running subTest2: Sub2"],
|
||||
["test-task-done", 1, "subTest2"],
|
||||
);
|
||||
} else {
|
||||
expectations.push(
|
||||
["test-task-start", "subTest2"],
|
||||
[
|
||||
"test-result", true,
|
||||
"Function threw, expecting error to match '/Missing required expectedError/', got 'Error: Missing required expectedError'"
|
||||
],
|
||||
["test-result", false, "Exception running subTest2: Sub2"],
|
||||
["test-task-done", 1, "subTest2"],
|
||||
);
|
||||
}
|
||||
expectations.push(["test-eq", true, "Expected exception.", "Sub2", "Sub2"]);
|
||||
|
||||
expectations.push(...[
|
||||
["test-message", "Ran test at", expectedProtocol],
|
||||
["test-message", "This is the last browser.test call"],
|
||||
@@ -290,6 +355,23 @@ add_task(async function test_test_in_background() {
|
||||
await extension.unload();
|
||||
});
|
||||
|
||||
add_task(async function test_test_in_wpt_mode() {
|
||||
let extensionData = {
|
||||
background: `(${testScript})()`,
|
||||
useServiceWorker: false,
|
||||
};
|
||||
|
||||
await SpecialPowers.pushPrefEnv({ set: [["extensions.wpt.enabled", true]] });
|
||||
|
||||
let extension = loadExtensionAndInterceptTest(extensionData);
|
||||
await extension.startup();
|
||||
let results = await extension.awaitResults();
|
||||
verifyTestResults(results, "wpt mode", "moz-extension:", false, true);
|
||||
await extension.unload();
|
||||
|
||||
await SpecialPowers.popPrefEnv();
|
||||
});
|
||||
|
||||
add_task(async function test_test_in_background_service_worker() {
|
||||
if (!ExtensionTestUtils.isInBackgroundServiceWorkerTests()) {
|
||||
is(
|
||||
|
||||
Reference in New Issue
Block a user