From 8e5c5b3b046d76c0ea2f2433bfcbeed6451681fc Mon Sep 17 00:00:00 2001 From: Filip Skokan Date: Sun, 3 Aug 2025 13:31:39 +0200 Subject: [PATCH] test: update WPT resources,WebCryptoAPI,webstorage PR-URL: https://github.com/nodejs/node/pull/59311 Refs: https://github.com/nodejs/node/issues/58987 Refs: https://github.com/nodejs/node/issues/59310 Reviewed-By: Luigi Pinca Reviewed-By: James M Snell --- test/fixtures/wpt/README.md | 6 +- ...rves_bits_curve448.tentative.https.any.js} | 0 ...rves_keys_curve448.tentative.https.any.js} | 0 ... => failures_Ed448.tentative.https.any.js} | 0 ...s => failures_X448.tentative.https.any.js} | 0 ...=> successes_Ed448.tentative.https.any.js} | 0 ... => successes_X448.tentative.https.any.js} | 0 .../wpt/WebCryptoAPI/getRandomValues.any.js | 6 +- ...kp_importKey_Ed448.tentative.https.any.js} | 0 ...okp_importKey_X448.tentative.https.any.js} | 0 ...Key_failures_Ed448.tentative.https.any.js} | 0 ...tKey_failures_X448.tentative.https.any.js} | 0 ... => eddsa_curve448.tentative.https.any.js} | 0 test/fixtures/wpt/resources/channel.sub.js | 2 +- .../fixtures/wpt/resources/check-layout-th.js | 3 +- test/fixtures/wpt/resources/check-layout.js | 245 --- .../declarative-shadow-dom-polyfill.js | 25 - .../wpt/resources/idlharness-shadowrealm.js | 61 - test/fixtures/wpt/resources/idlharness.js | 31 +- .../wpt/resources/out-of-scope-test.js | 5 + .../wpt/resources/testdriver-actions.js | 4 +- .../resources/testdriver-actions.js.headers | 2 + test/fixtures/wpt/resources/testdriver.js | 1366 ++++++++++++++++- ...rness-shadowrealm-audioworkletprocessor.js | 52 + .../testharness-shadowrealm-inner.js | 38 + .../testharness-shadowrealm-outer.js | 151 ++ test/fixtures/wpt/resources/testharness.js | 656 +++++--- .../wpt/resources/testharnessreport.js | 25 - .../wpt/resources/web-bluetooth-bidi-test.js | 408 +++++ test/fixtures/wpt/versions.json | 6 +- ...e_local_setitem_quotaexceedederr.window.js | 12 +- ...session_setitem_quotaexceedederr.window.js | 12 +- .../wpt/webstorage/symbol-props.window.js | 6 +- test/wpt/status/WebCryptoAPI.cjs | 16 + test/wpt/status/webstorage.json | 25 + 35 files changed, 2570 insertions(+), 593 deletions(-) rename test/fixtures/wpt/WebCryptoAPI/derive_bits_keys/{cfrg_curves_bits_curve448.https.any.js => cfrg_curves_bits_curve448.tentative.https.any.js} (100%) rename test/fixtures/wpt/WebCryptoAPI/derive_bits_keys/{cfrg_curves_keys_curve448.https.any.js => cfrg_curves_keys_curve448.tentative.https.any.js} (100%) rename test/fixtures/wpt/WebCryptoAPI/generateKey/{failures_Ed448.https.any.js => failures_Ed448.tentative.https.any.js} (100%) rename test/fixtures/wpt/WebCryptoAPI/generateKey/{failures_X448.https.any.js => failures_X448.tentative.https.any.js} (100%) rename test/fixtures/wpt/WebCryptoAPI/generateKey/{successes_Ed448.https.any.js => successes_Ed448.tentative.https.any.js} (100%) rename test/fixtures/wpt/WebCryptoAPI/generateKey/{successes_X448.https.any.js => successes_X448.tentative.https.any.js} (100%) rename test/fixtures/wpt/WebCryptoAPI/import_export/{okp_importKey_Ed448.https.any.js => okp_importKey_Ed448.tentative.https.any.js} (100%) rename test/fixtures/wpt/WebCryptoAPI/import_export/{okp_importKey_X448.https.any.js => okp_importKey_X448.tentative.https.any.js} (100%) rename test/fixtures/wpt/WebCryptoAPI/import_export/{okp_importKey_failures_Ed448.https.any.js => okp_importKey_failures_Ed448.tentative.https.any.js} (100%) rename test/fixtures/wpt/WebCryptoAPI/import_export/{okp_importKey_failures_X448.https.any.js => okp_importKey_failures_X448.tentative.https.any.js} (100%) rename test/fixtures/wpt/WebCryptoAPI/sign_verify/{eddsa_curve448.https.any.js => eddsa_curve448.tentative.https.any.js} (100%) delete mode 100644 test/fixtures/wpt/resources/check-layout.js delete mode 100644 test/fixtures/wpt/resources/declarative-shadow-dom-polyfill.js delete mode 100644 test/fixtures/wpt/resources/idlharness-shadowrealm.js create mode 100644 test/fixtures/wpt/resources/out-of-scope-test.js create mode 100644 test/fixtures/wpt/resources/testdriver-actions.js.headers create mode 100644 test/fixtures/wpt/resources/testharness-shadowrealm-audioworkletprocessor.js create mode 100644 test/fixtures/wpt/resources/testharness-shadowrealm-inner.js create mode 100644 test/fixtures/wpt/resources/testharness-shadowrealm-outer.js create mode 100644 test/fixtures/wpt/resources/web-bluetooth-bidi-test.js diff --git a/test/fixtures/wpt/README.md b/test/fixtures/wpt/README.md index 34aed56641d..06872e85e55 100644 --- a/test/fixtures/wpt/README.md +++ b/test/fixtures/wpt/README.md @@ -26,7 +26,7 @@ Last update: - interfaces: https://github.com/web-platform-tests/wpt/tree/e1b27be06b/interfaces - performance-timeline: https://github.com/web-platform-tests/wpt/tree/94caab7038/performance-timeline - resource-timing: https://github.com/web-platform-tests/wpt/tree/22d38586d0/resource-timing -- resources: https://github.com/web-platform-tests/wpt/tree/1e140d63ec/resources +- resources: https://github.com/web-platform-tests/wpt/tree/1d2c5fb36a/resources - streams: https://github.com/web-platform-tests/wpt/tree/bc9dcbbf1a/streams - url: https://github.com/web-platform-tests/wpt/tree/9504a83e01/url - urlpattern: https://github.com/web-platform-tests/wpt/tree/84b75f0880/urlpattern @@ -34,10 +34,10 @@ Last update: - wasm/jsapi: https://github.com/web-platform-tests/wpt/tree/cde25e7e3c/wasm/jsapi - wasm/webapi: https://github.com/web-platform-tests/wpt/tree/fd1b23eeaa/wasm/webapi - web-locks: https://github.com/web-platform-tests/wpt/tree/10a122a6bc/web-locks -- WebCryptoAPI: https://github.com/web-platform-tests/wpt/tree/ab08796857/WebCryptoAPI +- WebCryptoAPI: https://github.com/web-platform-tests/wpt/tree/1d2c5fb36a/WebCryptoAPI - webidl/ecmascript-binding/es-exceptions: https://github.com/web-platform-tests/wpt/tree/2f96fa1996/webidl/ecmascript-binding/es-exceptions - webmessaging/broadcastchannel: https://github.com/web-platform-tests/wpt/tree/6495c91853/webmessaging/broadcastchannel -- webstorage: https://github.com/web-platform-tests/wpt/tree/1291340aaa/webstorage +- webstorage: https://github.com/web-platform-tests/wpt/tree/1d2c5fb36a/webstorage [Web Platform Tests]: https://github.com/web-platform-tests/wpt [`git node wpt`]: https://github.com/nodejs/node-core-utils/blob/main/docs/git-node.md#git-node-wpt diff --git a/test/fixtures/wpt/WebCryptoAPI/derive_bits_keys/cfrg_curves_bits_curve448.https.any.js b/test/fixtures/wpt/WebCryptoAPI/derive_bits_keys/cfrg_curves_bits_curve448.tentative.https.any.js similarity index 100% rename from test/fixtures/wpt/WebCryptoAPI/derive_bits_keys/cfrg_curves_bits_curve448.https.any.js rename to test/fixtures/wpt/WebCryptoAPI/derive_bits_keys/cfrg_curves_bits_curve448.tentative.https.any.js diff --git a/test/fixtures/wpt/WebCryptoAPI/derive_bits_keys/cfrg_curves_keys_curve448.https.any.js b/test/fixtures/wpt/WebCryptoAPI/derive_bits_keys/cfrg_curves_keys_curve448.tentative.https.any.js similarity index 100% rename from test/fixtures/wpt/WebCryptoAPI/derive_bits_keys/cfrg_curves_keys_curve448.https.any.js rename to test/fixtures/wpt/WebCryptoAPI/derive_bits_keys/cfrg_curves_keys_curve448.tentative.https.any.js diff --git a/test/fixtures/wpt/WebCryptoAPI/generateKey/failures_Ed448.https.any.js b/test/fixtures/wpt/WebCryptoAPI/generateKey/failures_Ed448.tentative.https.any.js similarity index 100% rename from test/fixtures/wpt/WebCryptoAPI/generateKey/failures_Ed448.https.any.js rename to test/fixtures/wpt/WebCryptoAPI/generateKey/failures_Ed448.tentative.https.any.js diff --git a/test/fixtures/wpt/WebCryptoAPI/generateKey/failures_X448.https.any.js b/test/fixtures/wpt/WebCryptoAPI/generateKey/failures_X448.tentative.https.any.js similarity index 100% rename from test/fixtures/wpt/WebCryptoAPI/generateKey/failures_X448.https.any.js rename to test/fixtures/wpt/WebCryptoAPI/generateKey/failures_X448.tentative.https.any.js diff --git a/test/fixtures/wpt/WebCryptoAPI/generateKey/successes_Ed448.https.any.js b/test/fixtures/wpt/WebCryptoAPI/generateKey/successes_Ed448.tentative.https.any.js similarity index 100% rename from test/fixtures/wpt/WebCryptoAPI/generateKey/successes_Ed448.https.any.js rename to test/fixtures/wpt/WebCryptoAPI/generateKey/successes_Ed448.tentative.https.any.js diff --git a/test/fixtures/wpt/WebCryptoAPI/generateKey/successes_X448.https.any.js b/test/fixtures/wpt/WebCryptoAPI/generateKey/successes_X448.tentative.https.any.js similarity index 100% rename from test/fixtures/wpt/WebCryptoAPI/generateKey/successes_X448.https.any.js rename to test/fixtures/wpt/WebCryptoAPI/generateKey/successes_X448.tentative.https.any.js diff --git a/test/fixtures/wpt/WebCryptoAPI/getRandomValues.any.js b/test/fixtures/wpt/WebCryptoAPI/getRandomValues.any.js index 574134eb76d..aecd38efd60 100644 --- a/test/fixtures/wpt/WebCryptoAPI/getRandomValues.any.js +++ b/test/fixtures/wpt/WebCryptoAPI/getRandomValues.any.js @@ -60,9 +60,9 @@ for (const array of arrays) { test(function() { const maxlength = 65536 / ctor.BYTES_PER_ELEMENT; - assert_throws_dom("QuotaExceededError", function() { - self.crypto.getRandomValues(new ctor(maxlength + 1)) - }, "crypto.getRandomValues length over 65536") + assert_throws_quotaexceedederror(() => { + self.crypto.getRandomValues(new ctor(maxlength + 1)); + }, null, null, "crypto.getRandomValues length over 65536"); }, "Large length: " + array); test(function() { diff --git a/test/fixtures/wpt/WebCryptoAPI/import_export/okp_importKey_Ed448.https.any.js b/test/fixtures/wpt/WebCryptoAPI/import_export/okp_importKey_Ed448.tentative.https.any.js similarity index 100% rename from test/fixtures/wpt/WebCryptoAPI/import_export/okp_importKey_Ed448.https.any.js rename to test/fixtures/wpt/WebCryptoAPI/import_export/okp_importKey_Ed448.tentative.https.any.js diff --git a/test/fixtures/wpt/WebCryptoAPI/import_export/okp_importKey_X448.https.any.js b/test/fixtures/wpt/WebCryptoAPI/import_export/okp_importKey_X448.tentative.https.any.js similarity index 100% rename from test/fixtures/wpt/WebCryptoAPI/import_export/okp_importKey_X448.https.any.js rename to test/fixtures/wpt/WebCryptoAPI/import_export/okp_importKey_X448.tentative.https.any.js diff --git a/test/fixtures/wpt/WebCryptoAPI/import_export/okp_importKey_failures_Ed448.https.any.js b/test/fixtures/wpt/WebCryptoAPI/import_export/okp_importKey_failures_Ed448.tentative.https.any.js similarity index 100% rename from test/fixtures/wpt/WebCryptoAPI/import_export/okp_importKey_failures_Ed448.https.any.js rename to test/fixtures/wpt/WebCryptoAPI/import_export/okp_importKey_failures_Ed448.tentative.https.any.js diff --git a/test/fixtures/wpt/WebCryptoAPI/import_export/okp_importKey_failures_X448.https.any.js b/test/fixtures/wpt/WebCryptoAPI/import_export/okp_importKey_failures_X448.tentative.https.any.js similarity index 100% rename from test/fixtures/wpt/WebCryptoAPI/import_export/okp_importKey_failures_X448.https.any.js rename to test/fixtures/wpt/WebCryptoAPI/import_export/okp_importKey_failures_X448.tentative.https.any.js diff --git a/test/fixtures/wpt/WebCryptoAPI/sign_verify/eddsa_curve448.https.any.js b/test/fixtures/wpt/WebCryptoAPI/sign_verify/eddsa_curve448.tentative.https.any.js similarity index 100% rename from test/fixtures/wpt/WebCryptoAPI/sign_verify/eddsa_curve448.https.any.js rename to test/fixtures/wpt/WebCryptoAPI/sign_verify/eddsa_curve448.tentative.https.any.js diff --git a/test/fixtures/wpt/resources/channel.sub.js b/test/fixtures/wpt/resources/channel.sub.js index d93b3b30919..370d4f5905e 100644 --- a/test/fixtures/wpt/resources/channel.sub.js +++ b/test/fixtures/wpt/resources/channel.sub.js @@ -511,7 +511,7 @@ * * @param {SendChannel|string} [dest] - Either a SendChannel * to the destination, or the UUID of the destination. If - * omitted, a new UUID is generated, which can be used when + * ommitted, a new UUID is generated, which can be used when * constructing the URL for the global. * */ diff --git a/test/fixtures/wpt/resources/check-layout-th.js b/test/fixtures/wpt/resources/check-layout-th.js index f14ca3246b8..2965a25d146 100644 --- a/test/fixtures/wpt/resources/check-layout-th.js +++ b/test/fixtures/wpt/resources/check-layout-th.js @@ -218,6 +218,7 @@ window.checkLayout = function(selectorList, callDone = true) nodes = Array.prototype.slice.call(nodes); var checkedLayout = false; Array.prototype.forEach.call(nodes, function(node) { + const title = node.title ? `: ${node.title}` : ''; test(function(t) { var container = node.parentNode.className == 'container' ? node.parentNode : node; var prefix = @@ -240,7 +241,7 @@ window.checkLayout = function(selectorList, callDone = true) } checkedLayout |= !passed; } - }, selectorList + ' ' + String(++testNumber)); + }, `${selectorList} ${++testNumber}${title}`); }); if (!checkedLayout) { console.error("No valid data-* attributes found in selector list : " + selectorList); diff --git a/test/fixtures/wpt/resources/check-layout.js b/test/fixtures/wpt/resources/check-layout.js deleted file mode 100644 index 8634481497d..00000000000 --- a/test/fixtures/wpt/resources/check-layout.js +++ /dev/null @@ -1,245 +0,0 @@ -(function() { - -function insertAfter(nodeToAdd, referenceNode) -{ - if (referenceNode == document.body) { - document.body.appendChild(nodeToAdd); - return; - } - - if (referenceNode.nextSibling) - referenceNode.parentNode.insertBefore(nodeToAdd, referenceNode.nextSibling); - else - referenceNode.parentNode.appendChild(nodeToAdd); -} - -function positionedAncestor(node) -{ - var ancestor = node.parentNode; - while (getComputedStyle(ancestor).position == 'static') - ancestor = ancestor.parentNode; - return ancestor; -} - -function checkSubtreeExpectedValues(parent, failures) -{ - var checkedLayout = checkExpectedValues(parent, failures); - Array.prototype.forEach.call(parent.childNodes, function(node) { - checkedLayout |= checkSubtreeExpectedValues(node, failures); - }); - return checkedLayout; -} - -function checkAttribute(output, node, attribute) -{ - var result = node.getAttribute && node.getAttribute(attribute); - output.checked |= !!result; - return result; -} - -function checkExpectedValues(node, failures) -{ - var output = { checked: false }; - var expectedWidth = checkAttribute(output, node, "data-expected-width"); - if (expectedWidth) { - if (isNaN(expectedWidth) || Math.abs(node.offsetWidth - expectedWidth) >= 1) - failures.push("Expected " + expectedWidth + " for width, but got " + node.offsetWidth + ". "); - } - - var expectedHeight = checkAttribute(output, node, "data-expected-height"); - if (expectedHeight) { - if (isNaN(expectedHeight) || Math.abs(node.offsetHeight - expectedHeight) >= 1) - failures.push("Expected " + expectedHeight + " for height, but got " + node.offsetHeight + ". "); - } - - var expectedOffset = checkAttribute(output, node, "data-offset-x"); - if (expectedOffset) { - if (isNaN(expectedOffset) || Math.abs(node.offsetLeft - expectedOffset) >= 1) - failures.push("Expected " + expectedOffset + " for offsetLeft, but got " + node.offsetLeft + ". "); - } - - var expectedOffset = checkAttribute(output, node, "data-offset-y"); - if (expectedOffset) { - if (isNaN(expectedOffset) || Math.abs(node.offsetTop - expectedOffset) >= 1) - failures.push("Expected " + expectedOffset + " for offsetTop, but got " + node.offsetTop + ". "); - } - - var expectedOffset = checkAttribute(output, node, "data-positioned-offset-x"); - if (expectedOffset) { - var actualOffset = node.getBoundingClientRect().left - positionedAncestor(node).getBoundingClientRect().left; - if (isNaN(expectedOffset) || Math.abs(actualOffset - expectedOffset) >= 1) - failures.push("Expected " + expectedOffset + " for getBoundingClientRect().left offset, but got " + actualOffset + ". "); - } - - var expectedOffset = checkAttribute(output, node, "data-positioned-offset-y"); - if (expectedOffset) { - var actualOffset = node.getBoundingClientRect().top - positionedAncestor(node).getBoundingClientRect().top; - if (isNaN(expectedOffset) || Math.abs(actualOffset - expectedOffset) >= 1) - failures.push("Expected " + expectedOffset + " for getBoundingClientRect().top offset, but got " + actualOffset + ". "); - } - - var expectedWidth = checkAttribute(output, node, "data-expected-client-width"); - if (expectedWidth) { - if (isNaN(expectedWidth) || Math.abs(node.clientWidth - expectedWidth) >= 1) - failures.push("Expected " + expectedWidth + " for clientWidth, but got " + node.clientWidth + ". "); - } - - var expectedHeight = checkAttribute(output, node, "data-expected-client-height"); - if (expectedHeight) { - if (isNaN(expectedHeight) || Math.abs(node.clientHeight - expectedHeight) >= 1) - failures.push("Expected " + expectedHeight + " for clientHeight, but got " + node.clientHeight + ". "); - } - - var expectedWidth = checkAttribute(output, node, "data-expected-scroll-width"); - if (expectedWidth) { - if (isNaN(expectedWidth) || Math.abs(node.scrollWidth - expectedWidth) >= 1) - failures.push("Expected " + expectedWidth + " for scrollWidth, but got " + node.scrollWidth + ". "); - } - - var expectedHeight = checkAttribute(output, node, "data-expected-scroll-height"); - if (expectedHeight) { - if (isNaN(expectedHeight) || Math.abs(node.scrollHeight - expectedHeight) >= 1) - failures.push("Expected " + expectedHeight + " for scrollHeight, but got " + node.scrollHeight + ". "); - } - - var expectedOffset = checkAttribute(output, node, "data-total-x"); - if (expectedOffset) { - var totalLeft = node.clientLeft + node.offsetLeft; - if (isNaN(expectedOffset) || Math.abs(totalLeft - expectedOffset) >= 1) - failures.push("Expected " + expectedOffset + " for clientLeft+offsetLeft, but got " + totalLeft + ", clientLeft: " + node.clientLeft + ", offsetLeft: " + node.offsetLeft + ". "); - } - - var expectedOffset = checkAttribute(output, node, "data-total-y"); - if (expectedOffset) { - var totalTop = node.clientTop + node.offsetTop; - if (isNaN(expectedOffset) || Math.abs(totalTop - expectedOffset) >= 1) - failures.push("Expected " + expectedOffset + " for clientTop+offsetTop, but got " + totalTop + ", clientTop: " + node.clientTop + ", + offsetTop: " + node.offsetTop + ". "); - } - - var expectedDisplay = checkAttribute(output, node, "data-expected-display"); - if (expectedDisplay) { - var actualDisplay = getComputedStyle(node).display; - if (actualDisplay != expectedDisplay) - failures.push("Expected " + expectedDisplay + " for display, but got " + actualDisplay + ". "); - } - - var expectedPaddingTop = checkAttribute(output, node, "data-expected-padding-top"); - if (expectedPaddingTop) { - var actualPaddingTop = getComputedStyle(node).paddingTop; - // Trim the unit "px" from the output. - actualPaddingTop = actualPaddingTop.substring(0, actualPaddingTop.length - 2); - if (actualPaddingTop != expectedPaddingTop) - failures.push("Expected " + expectedPaddingTop + " for padding-top, but got " + actualPaddingTop + ". "); - } - - var expectedPaddingBottom = checkAttribute(output, node, "data-expected-padding-bottom"); - if (expectedPaddingBottom) { - var actualPaddingBottom = getComputedStyle(node).paddingBottom; - // Trim the unit "px" from the output. - actualPaddingBottom = actualPaddingBottom.substring(0, actualPaddingBottom.length - 2); - if (actualPaddingBottom != expectedPaddingBottom) - failures.push("Expected " + expectedPaddingBottom + " for padding-bottom, but got " + actualPaddingBottom + ". "); - } - - var expectedPaddingLeft = checkAttribute(output, node, "data-expected-padding-left"); - if (expectedPaddingLeft) { - var actualPaddingLeft = getComputedStyle(node).paddingLeft; - // Trim the unit "px" from the output. - actualPaddingLeft = actualPaddingLeft.substring(0, actualPaddingLeft.length - 2); - if (actualPaddingLeft != expectedPaddingLeft) - failures.push("Expected " + expectedPaddingLeft + " for padding-left, but got " + actualPaddingLeft + ". "); - } - - var expectedPaddingRight = checkAttribute(output, node, "data-expected-padding-right"); - if (expectedPaddingRight) { - var actualPaddingRight = getComputedStyle(node).paddingRight; - // Trim the unit "px" from the output. - actualPaddingRight = actualPaddingRight.substring(0, actualPaddingRight.length - 2); - if (actualPaddingRight != expectedPaddingRight) - failures.push("Expected " + expectedPaddingRight + " for padding-right, but got " + actualPaddingRight + ". "); - } - - var expectedMarginTop = checkAttribute(output, node, "data-expected-margin-top"); - if (expectedMarginTop) { - var actualMarginTop = getComputedStyle(node).marginTop; - // Trim the unit "px" from the output. - actualMarginTop = actualMarginTop.substring(0, actualMarginTop.length - 2); - if (actualMarginTop != expectedMarginTop) - failures.push("Expected " + expectedMarginTop + " for margin-top, but got " + actualMarginTop + ". "); - } - - var expectedMarginBottom = checkAttribute(output, node, "data-expected-margin-bottom"); - if (expectedMarginBottom) { - var actualMarginBottom = getComputedStyle(node).marginBottom; - // Trim the unit "px" from the output. - actualMarginBottom = actualMarginBottom.substring(0, actualMarginBottom.length - 2); - if (actualMarginBottom != expectedMarginBottom) - failures.push("Expected " + expectedMarginBottom + " for margin-bottom, but got " + actualMarginBottom + ". "); - } - - var expectedMarginLeft = checkAttribute(output, node, "data-expected-margin-left"); - if (expectedMarginLeft) { - var actualMarginLeft = getComputedStyle(node).marginLeft; - // Trim the unit "px" from the output. - actualMarginLeft = actualMarginLeft.substring(0, actualMarginLeft.length - 2); - if (actualMarginLeft != expectedMarginLeft) - failures.push("Expected " + expectedMarginLeft + " for margin-left, but got " + actualMarginLeft + ". "); - } - - var expectedMarginRight = checkAttribute(output, node, "data-expected-margin-right"); - if (expectedMarginRight) { - var actualMarginRight = getComputedStyle(node).marginRight; - // Trim the unit "px" from the output. - actualMarginRight = actualMarginRight.substring(0, actualMarginRight.length - 2); - if (actualMarginRight != expectedMarginRight) - failures.push("Expected " + expectedMarginRight + " for margin-right, but got " + actualMarginRight + ". "); - } - - return output.checked; -} - -window.checkLayout = function(selectorList, outputContainer) -{ - var result = true; - if (!selectorList) { - document.body.appendChild(document.createTextNode("You must provide a CSS selector of nodes to check.")); - return; - } - var nodes = document.querySelectorAll(selectorList); - nodes = Array.prototype.slice.call(nodes); - nodes.reverse(); - var checkedLayout = false; - Array.prototype.forEach.call(nodes, function(node) { - var failures = []; - checkedLayout |= checkExpectedValues(node.parentNode, failures); - checkedLayout |= checkSubtreeExpectedValues(node, failures); - - var container = node.parentNode.className == 'container' ? node.parentNode : node; - - var pre = document.createElement('pre'); - if (failures.length) { - pre.className = 'FAIL'; - result = false; - } - pre.appendChild(document.createTextNode(failures.length ? "FAIL:\n" + failures.join('\n') + '\n\n' + container.outerHTML : "PASS")); - - var referenceNode = container; - if (outputContainer) { - if (!outputContainer.lastChild) { - // Inserting a text node so we have something to insertAfter. - outputContainer.textContent = " "; - } - referenceNode = outputContainer.lastChild; - } - insertAfter(pre, referenceNode); - }); - - if (!checkedLayout) { - document.body.appendChild(document.createTextNode("FAIL: No valid data-* attributes found in selector list : " + selectorList)); - return false; - } - - return result; -} - -})(); diff --git a/test/fixtures/wpt/resources/declarative-shadow-dom-polyfill.js b/test/fixtures/wpt/resources/declarative-shadow-dom-polyfill.js deleted file mode 100644 index 99a3e911eb6..00000000000 --- a/test/fixtures/wpt/resources/declarative-shadow-dom-polyfill.js +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Polyfill for attaching shadow trees for declarative Shadow DOM for - * implementations that do not support declarative Shadow DOM. - * - * Note: this polyfill will feature-detect the native feature, and do nothing - * if supported. - * - * See: https://github.com/whatwg/html/pull/5465 - * - * root: The root of the subtree in which to upgrade shadow roots - * - */ - -function polyfill_declarative_shadow_dom(root) { - if (HTMLTemplateElement.prototype.hasOwnProperty('shadowRootMode')) - return; - root.querySelectorAll("template[shadowrootmode]").forEach(template => { - const mode = template.getAttribute("shadowrootmode"); - const delegatesFocus = template.hasAttribute("shadowrootdelegatesfocus"); - const shadowRoot = template.parentNode.attachShadow({ mode, delegatesFocus }); - shadowRoot.appendChild(template.content); - template.remove(); - polyfill_declarative_shadow_dom(shadowRoot); - }); -} diff --git a/test/fixtures/wpt/resources/idlharness-shadowrealm.js b/test/fixtures/wpt/resources/idlharness-shadowrealm.js deleted file mode 100644 index 9484ca6f512..00000000000 --- a/test/fixtures/wpt/resources/idlharness-shadowrealm.js +++ /dev/null @@ -1,61 +0,0 @@ -// TODO: it would be nice to support `idl_array.add_objects` -function fetch_text(url) { - return fetch(url).then(function (r) { - if (!r.ok) { - throw new Error("Error fetching " + url + "."); - } - return r.text(); - }); -} - -/** - * idl_test_shadowrealm is a promise_test wrapper that handles the fetching of the IDL, and - * running the code in a `ShadowRealm`, avoiding repetitive boilerplate. - * - * @see https://github.com/tc39/proposal-shadowrealm - * @param {String[]} srcs Spec name(s) for source idl files (fetched from - * /interfaces/{name}.idl). - * @param {String[]} deps Spec name(s) for dependency idl files (fetched - * from /interfaces/{name}.idl). Order is important - dependencies from - * each source will only be included if they're already know to be a - * dependency (i.e. have already been seen). - */ -function idl_test_shadowrealm(srcs, deps) { - promise_setup(async t => { - const realm = new ShadowRealm(); - // https://github.com/web-platform-tests/wpt/issues/31996 - realm.evaluate("globalThis.self = globalThis; undefined;"); - - realm.evaluate(` - globalThis.self.GLOBAL = { - isWindow: function() { return false; }, - isWorker: function() { return false; }, - isShadowRealm: function() { return true; }, - }; undefined; - `); - const specs = await Promise.all(srcs.concat(deps).map(spec => { - return fetch_text("/interfaces/" + spec + ".idl"); - })); - const idls = JSON.stringify(specs); - await new Promise( - realm.evaluate(`(resolve,reject) => { - (async () => { - await import("/resources/testharness.js"); - await import("/resources/WebIDLParser.js"); - await import("/resources/idlharness.js"); - const idls = ${idls}; - const idl_array = new IdlArray(); - for (let i = 0; i < ${srcs.length}; i++) { - idl_array.add_idls(idls[i]); - } - for (let i = ${srcs.length}; i < ${srcs.length + deps.length}; i++) { - idl_array.add_dependency_idls(idls[i]); - } - idl_array.test(); - })().then(resolve, (e) => reject(e.toString())); - }`) - ); - await fetch_tests_from_shadow_realm(realm); - }); -} -// vim: set expandtab shiftwidth=4 tabstop=4 foldmarker=@{,@} foldmethod=marker: diff --git a/test/fixtures/wpt/resources/idlharness.js b/test/fixtures/wpt/resources/idlharness.js index 056fcdd4a10..2eb710c1827 100644 --- a/test/fixtures/wpt/resources/idlharness.js +++ b/test/fixtures/wpt/resources/idlharness.js @@ -566,6 +566,7 @@ IdlArray.prototype.is_json_type = function(type) case "Uint8ClampedArray": case "BigInt64Array": case "BigUint64Array": + case "Float16Array": case "Float32Array": case "Float64Array": case "ArrayBuffer": @@ -733,7 +734,7 @@ IdlArray.prototype.test = function() Object.getOwnPropertyNames(this.members).forEach(function(memberName) { var member = this.members[memberName]; - if (!(member instanceof IdlInterface)) { + if (!(member instanceof IdlInterface || member instanceof IdlNamespace)) { return; } @@ -782,6 +783,10 @@ IdlArray.prototype.merge_partials = function() } testedPartials.set(parsed_idl.name, partialTestCount); + if (!self.shouldRunSubTest(partialTestName)) { + return; + } + if (!parsed_idl.untested) { test(function () { assert_true(originalExists, `Original ${parsed_idl.type} should be defined`); @@ -809,7 +814,7 @@ IdlArray.prototype.merge_partials = function() { // Special-case "Exposed". Must be a subset of original interface's exposure. // Exposed on a partial is the equivalent of having the same Exposed on all nested members. - // See https://github.com/heycam/webidl/issues/154 for discrepancy between Exposed and + // See https://github.com/heycam/webidl/issues/154 for discrepency between Exposed and // other extended attributes on partial interfaces. const exposureAttr = parsed_idl.extAttrs.find(a => a.name === "Exposed"); if (exposureAttr) { @@ -870,6 +875,7 @@ IdlArray.prototype.merge_mixins = function() { const lhs = parsed_idl.target; const rhs = parsed_idl.includes; + const testName = lhs + " includes " + rhs + ": member names are unique"; var errStr = lhs + " includes " + rhs + ", but "; if (!(lhs in this.members)) throw errStr + lhs + " is undefined."; @@ -877,7 +883,7 @@ IdlArray.prototype.merge_mixins = function() if (!(rhs in this.members)) throw errStr + rhs + " is undefined."; if (!(this.members[rhs] instanceof IdlInterface)) throw errStr + rhs + " is not an interface."; - if (this.members[rhs].members.length) { + if (this.members[rhs].members.length && self.shouldRunSubTest(testName)) { test(function () { var clash = this.members[rhs].members.find(function(member) { return this.members[lhs].members.find(function(m) { @@ -891,7 +897,7 @@ IdlArray.prototype.merge_mixins = function() this.members[lhs].members.push(new IdlInterfaceMember(member)); }.bind(this)); assert_true(!clash, "member " + (clash && clash.name) + " is unique"); - }.bind(this), lhs + " includes " + rhs + ": member names are unique"); + }.bind(this), testName); } } this.includes = []; @@ -1420,7 +1426,7 @@ IdlInterface.prototype.test = function() if (!this.untested) { subsetTestByKey(this.name, test, function() { - assert_false(this.name in self); + assert_false(this.name in self, this.name + " interface should not exist"); }.bind(this), this.name + " interface: existence and properties of interface object"); } return; @@ -3450,6 +3456,17 @@ IdlNamespace.prototype.test_self = function () IdlNamespace.prototype.test = function () { + // If the namespace object is not exposed, only test that. Members can't be + // tested either + if (!this.exposed) { + if (!this.untested) { + subsetTestByKey(this.name, test, function() { + assert_false(this.name in self, this.name + " namespace should not exist"); + }.bind(this), this.name + " namespace: existence and properties of namespace object"); + } + return; + } + if (!this.untested) { this.test_self(); } @@ -3497,7 +3514,7 @@ function idl_test(srcs, deps, idl_setup_func) { "require-exposed" ]; return Promise.all( - srcs.concat(deps).map(fetch_spec)) + srcs.concat(deps).map(globalThis.fetch_spec)) .then(function(results) { const astArray = results.map(result => WebIDL2.parse(result.idl, { sourceName: result.spec }) @@ -3538,9 +3555,11 @@ function idl_test(srcs, deps, idl_setup_func) { }); }, 'idl_test setup'); } +globalThis.idl_test = idl_test; /** * fetch_spec is a shorthand for a Promise that fetches the spec's content. + * Note: ShadowRealm-specific implementation in testharness-shadowrealm-inner.js */ function fetch_spec(spec) { var url = '/interfaces/' + spec + '.idl'; diff --git a/test/fixtures/wpt/resources/out-of-scope-test.js b/test/fixtures/wpt/resources/out-of-scope-test.js new file mode 100644 index 00000000000..ce24124646d --- /dev/null +++ b/test/fixtures/wpt/resources/out-of-scope-test.js @@ -0,0 +1,5 @@ +// Testing that the resolution is correct using `resolve`, as you can't import +// the same module twice. +window.outscope_test_result = import.meta.resolve("a"); +window.outscope_test_result2 = import.meta.resolve("../resources/log.sub.js?name=E"); + diff --git a/test/fixtures/wpt/resources/testdriver-actions.js b/test/fixtures/wpt/resources/testdriver-actions.js index e550ff0b1dc..edb4759954d 100644 --- a/test/fixtures/wpt/resources/testdriver-actions.js +++ b/test/fixtures/wpt/resources/testdriver-actions.js @@ -32,7 +32,7 @@ * await actions.send(); * * @param {number} [defaultTickDuration] - The default duration of a - * tick. Be default this is set to 16ms, which is one frame time + * tick. Be default this is set ot 16ms, which is one frame time * based on 60Hz display. */ function Actions(defaultTickDuration=16) { @@ -290,7 +290,7 @@ }, /** - * Create a keyDown event for the current default key source + * Create a keyUp event for the current default key source * * @param {String} key - Key to release * @param {String?} sourceName - Named key source to use or null for the default key source diff --git a/test/fixtures/wpt/resources/testdriver-actions.js.headers b/test/fixtures/wpt/resources/testdriver-actions.js.headers new file mode 100644 index 00000000000..5e8f640c665 --- /dev/null +++ b/test/fixtures/wpt/resources/testdriver-actions.js.headers @@ -0,0 +1,2 @@ +Content-Type: text/javascript; charset=utf-8 +Cache-Control: max-age=3600 diff --git a/test/fixtures/wpt/resources/testdriver.js b/test/fixtures/wpt/resources/testdriver.js index ddf723cb3ee..5b390dedeb7 100644 --- a/test/fixtures/wpt/resources/testdriver.js +++ b/test/fixtures/wpt/resources/testdriver.js @@ -3,6 +3,26 @@ var idCounter = 0; let testharness_context = null; + const features = (() => { + function getFeatures(scriptSrc) { + try { + const url = new URL(scriptSrc); + return url.searchParams.getAll('feature'); + } catch (e) { + return []; + } + } + + return getFeatures(document?.currentScript?.src ?? ''); + })(); + + function assertBidiIsEnabled(){ + if (!features.includes('bidi')) { + throw new Error( + "`?feature=bidi` is missing when importing testdriver.js but the test is using WebDriver BiDi APIs"); + } + } + function getInViewCenterPoint(rect) { var left = Math.max(0, rect.left); var right = Math.min(window.innerWidth, rect.right); @@ -49,6 +69,924 @@ * @namespace {test_driver} */ window.test_driver = { + /** + Represents `WebDriver BiDi `_ protocol. + */ + bidi: { + /** + * @typedef {(String|WindowProxy)} Context A browsing context. Can + * be specified by its ID (a string) or using a `WindowProxy` + * object. + */ + /** + * `bluetooth `_ module. + */ + bluetooth: { + /** + * Handle a bluetooth device prompt with the given params. Matches the + * `bluetooth.handleRequestDevicePrompt + * `_ + * WebDriver BiDi command. + * + * @example + * await test_driver.bidi.bluetooth.handleRequestDevicePrompt({ + * prompt: "pmt-e0a234b", + * accept: true, + * device: "dvc-9b3b872" + * }); + * + * @param {object} params - Parameters for the command. + * @param {string} params.prompt - The id of a bluetooth device prompt. + * Matches the + * `bluetooth.HandleRequestDevicePromptParameters:prompt `_ + * value. + * @param {bool} params.accept - Whether to accept a bluetooth device prompt. + * Matches the + * `bluetooth.HandleRequestDevicePromptAcceptParameters:accept `_ + * value. + * @param {string} params.device - The device id from a bluetooth device + * prompt to be accepted. Matches the + * `bluetooth.HandleRequestDevicePromptAcceptParameters:device `_ + * value. + * @param {Context} [params.context] The optional context parameter specifies in + * which browsing context the bluetooth device prompt should be handled. If not + * provided, the current browsing context is used. + * @returns {Promise} fulfilled after the bluetooth device prompt + * is handled, or rejected if the operation fails. + */ + handle_request_device_prompt: function(params) { + return window.test_driver_internal.bidi.bluetooth + .handle_request_device_prompt(params); + }, + /** + * Creates a simulated bluetooth adapter with the given params. Matches the + * `bluetooth.simulateAdapter `_ + * WebDriver BiDi command. + * + * @example + * await test_driver.bidi.bluetooth.simulate_adapter({ + * state: "powered-on" + * }); + * + * @param {object} params - Parameters for the command. + * @param {string} params.state The state of the simulated bluetooth adapter. + * Matches the + * `bluetooth.SimulateAdapterParameters:state `_ + * value. + * @param {Context} [params.context] The optional context parameter specifies in + * which browsing context the simulated bluetooth adapter should be set. If not + * provided, the current browsing context is used. + * @returns {Promise} fulfilled after the simulated bluetooth adapter is created + * and set, or rejected if the operation fails. + */ + simulate_adapter: function (params) { + return window.test_driver_internal.bidi.bluetooth.simulate_adapter(params); + }, + /** + * Disables the bluetooth simulation with the given params. Matches the + * `bluetooth.disableSimulation `_ + * WebDriver BiDi command. + * + * @example + * await test_driver.bidi.bluetooth.disable_simulation(); + * + * @param {object} params - Parameters for the command. + * @param {Context} [params.context] The optional context parameter specifies in + * which browsing context to disable the simulation for. If not provided, the + * current browsing context is used. + * @returns {Promise} fulfilled after the simulation is disabled, or rejected if + * the operation fails. + */ + disable_simulation: function (params) { + return window.test_driver_internal.bidi.bluetooth.disable_simulation(params); + }, + /** + * Creates a simulated bluetooth peripheral with the given params. + * Matches the + * `bluetooth.simulatePreconnectedPeripheral `_ + * WebDriver BiDi command. + * + * @example + * await test_driver.bidi.bluetooth.simulatePreconnectedPeripheral({ + * "address": "09:09:09:09:09:09", + * "name": "Some Device", + * "manufacturerData": [{key: 17, data: "AP8BAX8="}], + * "knownServiceUuids": [ + * "12345678-1234-5678-9abc-def123456789", + * ], + * }); + * + * @param {object} params - Parameters for the command. + * @param {string} params.address - The address of the simulated + * bluetooth peripheral. Matches the + * `bluetooth.SimulatePreconnectedPeripheralParameters:address `_ + * value. + * @param {string} params.name - The name of the simulated bluetooth + * peripheral. Matches the + * `bluetooth.SimulatePreconnectedPeripheralParameters:name `_ + * value. + * @param {Array.ManufacturerData} params.manufacturerData - The manufacturerData of the + * simulated bluetooth peripheral. Matches the + * `bluetooth.SimulatePreconnectedPeripheralParameters:manufacturerData `_ + * value. + * @param {string} params.knownServiceUuids - The knownServiceUuids of + * the simulated bluetooth peripheral. Matches the + * `bluetooth.SimulatePreconnectedPeripheralParameters:knownServiceUuids `_ + * value. + * @param {Context} [params.context] The optional context parameter + * specifies in which browsing context the simulated bluetooth peripheral should be + * set. If not provided, the current browsing context is used. + * @returns {Promise} fulfilled after the simulated bluetooth peripheral is created + * and set, or rejected if the operation fails. + */ + simulate_preconnected_peripheral: function(params) { + return window.test_driver_internal.bidi.bluetooth + .simulate_preconnected_peripheral(params); + }, + /** + * Simulates a GATT connection response for a given peripheral. + * Matches the `bluetooth.simulateGattConnectionResponse + * `_ + * WebDriver BiDi command. + * + * @example + * await test_driver.bidi.bluetooth.simulate_gatt_connection_response({ + * "address": "09:09:09:09:09:09", + * "code": 0x0 + * }); + * + * @param {object} params - Parameters for the command. + * @param {string} params.address - The address of the simulated + * bluetooth peripheral. Matches the + * `bluetooth.SimulateGattConnectionResponseParameters:peripheral `_ + * value. + * @param {number} params.code - The response code for a GATT connection attempted. + * Matches the + * `bluetooth.SimulateGattConnectionResponseParameters:code `_ + * value. + * @param {Context} [params.context] The optional context parameter specifies in + * which browsing context the GATT connection response should be simulated. If not + * provided, the current browsing context is used. + * @returns {Promise} fulfilled after the GATT connection response + * is simulated, or rejected if the operation fails. + */ + simulate_gatt_connection_response: function(params) { + return window.test_driver_internal.bidi.bluetooth + .simulate_gatt_connection_response(params); + }, + /** + * Simulates a GATT disconnection for a given peripheral. + * Matches the `bluetooth.simulateGattDisconnection + * `_ + * WebDriver BiDi command. + * + * @example + * await test_driver.bidi.bluetooth.simulate_gatt_disconnection({ + * "address": "09:09:09:09:09:09", + * }); + * + * @param {object} params - Parameters for the command. + * @param {string} params.address - The address of the simulated + * bluetooth peripheral. Matches the + * `bluetooth.SimulateGattDisconnectionParameters:address `_ + * value. + * @param {Context} [params.context] The optional context parameter specifies in + * which browsing context the GATT disconnection should be simulated. If not + * provided, the current browsing context is used. + * @returns {Promise} fulfilled after the GATT disconnection + * is simulated, or rejected if the operation fails. + */ + simulate_gatt_disconnection: function(params) { + return window.test_driver_internal.bidi.bluetooth + .simulate_gatt_disconnection(params); + }, + /** + * Simulates a GATT service. + * Matches the `bluetooth.simulateService + * `_ + * WebDriver BiDi command. + * + * @example + * await test_driver.bidi.bluetooth.simulate_service({ + * "address": "09:09:09:09:09:09", + * "uuid": "0000180d-0000-1000-8000-00805f9b34fb", + * "type": "add" + * }); + * + * @param {object} params - Parameters for the command. + * @param {string} params.address - The address of the simulated bluetooth peripheral this service belongs to. + * Matches the + * `bluetooth.SimulateServiceParameters:address `_ + * value. + * @param {string} params.uuid - The uuid of the simulated GATT service. + * Matches the + * `bluetooth.SimulateServiceParameters:address `_ + * value. + * @param {string} params.type - The type of the GATT service simulation, either "add" or "remove". + * Matches the + * `bluetooth.SimulateServiceParameters:address `_ + * value. + * @param {Context} [params.context] The optional context parameter specifies in + * which browsing context the GATT service should be simulated. If not + * provided, the current browsing context is used. + * @returns {Promise} fulfilled after the GATT service + * is simulated, or rejected if the operation fails. + */ + simulate_service: function(params) { + return window.test_driver_internal.bidi.bluetooth + .simulate_service(params); + }, + /** + * Simulates a GATT characteristic. + * Matches the `bluetooth.simulateCharacteristic + * `_ + * WebDriver BiDi command. + * + * @example + * await test_driver.bidi.bluetooth.simulate_characteristic({ + * "address": "09:09:09:09:09:09", + * "serviceUuid": "0000180d-0000-1000-8000-00805f9b34fb", + * "characteristicUuid": "00002a21-0000-1000-8000-00805f9b34fb", + * "characteristicProperties": { + * "read": true, + * "write": true, + * "notify": true + * }, + * "type": "add" + * }); + * + * @param {object} params - Parameters for the command. + * @param {string} params.address - The address of the simulated bluetooth peripheral the characterisitc belongs to. + * Matches the + * `bluetooth.SimulateCharacteristicParameters:address `_ + * value. + * @param {string} params.serviceUuid - The uuid of the simulated GATT service the characterisitc belongs to. + * Matches the + * `bluetooth.SimulateCharacteristicParameters:address `_ + * value. + * @param {string} params.characteristicUuid - The uuid of the simulated GATT characteristic. + * Matches the + * `bluetooth.SimulateCharacteristicParameters:address `_ + * value. + * @param {string} params.characteristicProperties - The properties of the simulated GATT characteristic. + * Matches the + * `bluetooth.SimulateCharacteristicParameters:address `_ + * value. + * @param {string} params.type - The type of the GATT characterisitc simulation, either "add" or "remove". + * Matches the + * `bluetooth.SimulateCharacteristicParameters:address `_ + * value. + * @param {Context} [params.context] The optional context parameter specifies in + * which browsing context the GATT characteristic should be simulated. If not + * provided, the current browsing context is used. + * @returns {Promise} fulfilled after the GATT characteristic + * is simulated, or rejected if the operation fails. + */ + simulate_characteristic: function(params) { + return window.test_driver_internal.bidi.bluetooth + .simulate_characteristic(params); + }, + /** + * Simulates a GATT characteristic response. + * Matches the `bluetooth.simulateCharacteristicResponse + * `_ + * WebDriver BiDi command. + * + * @example + * await test_driver.bidi.bluetooth.simulate_characteristic({ + * "address": "09:09:09:09:09:09", + * "serviceUuid": "0000180d-0000-1000-8000-00805f9b34fb", + * "characteristicUuid": "00002a21-0000-1000-8000-00805f9b34fb", + * "type": "read", + * "code": 0, + * "data": [1, 2] + * }); + * + * @param {object} params - Parameters for the command. + * @param {string} params.address - The address of the simulated + * bluetooth peripheral. Matches the + * `bluetooth.SimulateCharacteristicResponseParameters:address `_ + * value. + * @param {string} params.serviceUuid - The uuid of the simulated GATT service the characterisitc belongs to. + * Matches the + * `bluetooth.SimulateCharacteristicResponseParameters:address `_ + * value. + * @param {string} params.characteristicUuid - The uuid of the simulated characteristic. + * Matches the + * `bluetooth.SimulateCharacteristicResponseParameters:address `_ + * value. + * @param {string} params.type - The type of the simulated GATT characteristic operation." + * Can be "read", "write", "subscribe-to-notifications" or "unsubscribe-from-notifications". + * Matches the + * `bluetooth.SimulateCharacteristicResponseParameters:address `_ + * value. + * @param {string} params.code - The simulated GATT characteristic response code. + * Matches the + * `bluetooth.SimulateCharacteristicResponseParameters:address `_ + * value.* + * @param {string} params.data - The data along with the simulated GATT characteristic response. + * Matches the + * `bluetooth.SimulateCharacteristicResponseParameters:address `_ + * value.** + * @param {Context} [params.context] The optional context parameter specifies in + * which browsing context the GATT characteristic belongs to. If not + * provided, the current browsing context is used. + * @returns {Promise} fulfilled after the GATT characteristic + * is simulated, or rejected if the operation fails. + */ + simulate_characteristic_response: function(params) { + return window.test_driver_internal.bidi.bluetooth + .simulate_characteristic_response(params); + }, + /** + * Simulates a GATT descriptor. + * Matches the `bluetooth.simulateDescriptor + * `_ + * WebDriver BiDi command. + * + * @example + * await test_driver.bidi.bluetooth.simulate_descriptor({ + * "address": "09:09:09:09:09:09", + * "serviceUuid": "0000180d-0000-1000-8000-00805f9b34fb", + * "characteristicUuid": "00002a21-0000-1000-8000-00805f9b34fb", + * "descriptorUuid": "00002901-0000-1000-8000-00805f9b34fb", + * "type": "add" + * }); + * + * @param {object} params - Parameters for the command. + * @param {string} params.address - The address of the simulated bluetooth peripheral the descriptor belongs to. + * Matches the + * `bluetooth.SimulateDescriptorParameters:address `_ + * value. + * @param {string} params.serviceUuid - The uuid of the simulated GATT service the descriptor belongs to. + * Matches the + * `bluetooth.SimulateDescriptorParameters:address `_ + * value. + * @param {string} params.characteristicUuid - The uuid of the simulated GATT characterisitc the descriptor belongs to. + * Matches the + * `bluetooth.SimulateDescriptorParameters:address `_ + * value. + * @param {string} params.descriptorUuid - The uuid of the simulated GATT descriptor. + * Matches the + * `bluetooth.SimulateDescriptorParameters:address `_ + * value.* + * @param {string} params.type - The type of the GATT descriptor simulation, either "add" or "remove". + * Matches the + * `bluetooth.SimulateDescriptorParameters:address `_ + * value. + * @param {Context} [params.context] The optional context parameter specifies in + * which browsing context the GATT descriptor should be simulated. If not + * provided, the current browsing context is used. + * @returns {Promise} fulfilled after the GATT descriptor + * is simulated, or rejected if the operation fails. + */ + simulate_descriptor: function(params) { + return window.test_driver_internal.bidi.bluetooth + .simulate_descriptor(params); + }, + /** + * Simulates a GATT descriptor response. + * Matches the `bluetooth.simulateDescriptorResponse + * `_ + * WebDriver BiDi command. + * + * @example + * await test_driver.bidi.bluetooth.simulate_descriptor_response({ + * "address": "09:09:09:09:09:09", + * "serviceUuid": "0000180d-0000-1000-8000-00805f9b34fb", + * "characteristicUuid": "00002a21-0000-1000-8000-00805f9b34fb", + * "descriptorUuid": "00002901-0000-1000-8000-00805f9b34fb", + * "type": "read", + * "code": 0, + * "data": [1, 2] + * }); + * + * @param {object} params - Parameters for the command. + * @param {string} params.address - The address of the simulated bluetooth peripheral the descriptor belongs to. + * Matches the + * `bluetooth.SimulateDescriptorResponseParameters:address `_ + * value. + * @param {string} params.serviceUuid - The uuid of the simulated GATT service the descriptor belongs to. + * Matches the + * `bluetooth.SimulateDescriptorResponseParameters:address `_ + * value. + * @param {string} params.characteristicUuid - The uuid of the simulated GATT characterisitc the descriptor belongs to. + * Matches the + * `bluetooth.SimulateDescriptorResponseParameters:address `_ + * value. + * @param {string} params.descriptorUuid - The uuid of the simulated GATT descriptor. + * Matches the + * `bluetooth.SimulateDescriptorResponseParameters:address `_ + * value. + * @param {string} params.type - The type of the simulated GATT descriptor operation. + * Matches the + * `bluetooth.SimulateDescriptorResponseParameters:address `_ + * value. + * @param {string} params.code - The simulated GATT descriptor response code. + * Matches the + * `bluetooth.SimulateDescriptorResponseParameters:address `_ + * value.* + * @param {string} params.data - The data along with the simulated GATT descriptor response. + * Matches the + * `bluetooth.SimulateDescriptorResponseParameters:address `_ + * value.** + * @param {Context} [params.context] The optional context parameter specifies in + * which browsing context the GATT descriptor belongs to. If not + * provided, the current browsing context is used. + * @returns {Promise} fulfilled after the GATT descriptor response + * is simulated, or rejected if the operation fails. + */ + simulate_descriptor_response: function(params) { + return window.test_driver_internal.bidi.bluetooth + .simulate_descriptor_response(params); + }, + /** + * `bluetooth.RequestDevicePromptUpdatedParameters `_ + * event. + */ + request_device_prompt_updated: { + /** + * @typedef {object} RequestDevicePromptUpdated + * `bluetooth.RequestDevicePromptUpdatedParameters `_ + * event. + */ + + /** + * Subscribes to the event. Events will be emitted only if + * there is a subscription for the event. This method does + * not add actual listeners. To listen to the event, use the + * `on` or `once` methods. The buffered events will be + * emitted before the command promise is resolved. + * + * @param {object} [params] Parameters for the subscription. + * @param {null|Array.<(Context)>} [params.contexts] The + * optional contexts parameter specifies which browsing + * contexts to subscribe to the event on. It should be + * either an array of Context objects, or null. If null, the + * event will be subscribed to globally. If omitted, the + * event will be subscribed to on the current browsing + * context. + * @returns {Promise<(function(): Promise)>} Callback + * for unsubscribing from the created subscription. + */ + subscribe: async function(params = {}) { + assertBidiIsEnabled(); + return window.test_driver_internal.bidi.bluetooth + .request_device_prompt_updated.subscribe(params); + }, + /** + * Adds an event listener for the event. + * + * @param {function(RequestDevicePromptUpdated): void} callback The + * callback to be called when the event is emitted. The + * callback is called with the event object as a parameter. + * @returns {function(): void} A function that removes the + * added event listener when called. + */ + on: function(callback) { + assertBidiIsEnabled(); + return window.test_driver_internal.bidi.bluetooth + .request_device_prompt_updated.on(callback); + }, + /** + * Adds an event listener for the event that is only called + * once and removed afterward. + * + * @return {Promise} The promise which + * is resolved with the event object when the event is emitted. + */ + once: function() { + assertBidiIsEnabled(); + return new Promise(resolve => { + const remove_handler = + window.test_driver_internal.bidi.bluetooth + .request_device_prompt_updated.on(event => { + resolve(event); + remove_handler(); + }); + }); + }, + }, + /** + * `bluetooth.GattConnectionAttemptedParameters `_ + * event. + */ + gatt_connection_attempted: { + /** + * @typedef {object} GattConnectionAttempted + * `bluetooth.GattConnectionAttempted `_ + * event. + */ + + /** + * Subscribes to the event. Events will be emitted only if + * there is a subscription for the event. This method does + * not add actual listeners. To listen to the event, use the + * `on` or `once` methods. The buffered events will be + * emitted before the command promise is resolved. + * + * @param {object} [params] Parameters for the subscription. + * @param {null|Array.<(Context)>} [params.contexts] The + * optional contexts parameter specifies which browsing + * contexts to subscribe to the event on. It should be + * either an array of Context objects, or null. If null, the + * event will be subscribed to globally. If omitted, the + * event will be subscribed to on the current browsing + * context. + * @returns {Promise<(function(): Promise)>} Callback + * for unsubscribing from the created subscription. + */ + subscribe: async function(params = {}) { + assertBidiIsEnabled(); + return window.test_driver_internal.bidi.bluetooth + .gatt_connection_attempted.subscribe(params); + }, + /** + * Adds an event listener for the event. + * + * @param {function(GattConnectionAttempted): void} callback The + * callback to be called when the event is emitted. The + * callback is called with the event object as a parameter. + * @returns {function(): void} A function that removes the + * added event listener when called. + */ + on: function(callback) { + assertBidiIsEnabled(); + return window.test_driver_internal.bidi.bluetooth + .gatt_connection_attempted.on(callback); + }, + /** + * Adds an event listener for the event that is only called + * once and removed afterward. + * + * @return {Promise} The promise which + * is resolved with the event object when the event is emitted. + */ + once: function() { + assertBidiIsEnabled(); + return new Promise(resolve => { + const remove_handler = + window.test_driver_internal.bidi.bluetooth + .gatt_connection_attempted.on(event => { + resolve(event); + remove_handler(); + }); + }); + }, + }, + /** + * `bluetooth.CharacteristicEventGeneratedParameters `_ + * event. + */ + characteristic_event_generated: { + /** + * @typedef {object} CharacteristicEventGenerated + * `bluetooth.CharacteristicEventGenerated `_ + * event. + */ + + /** + * Subscribes to the event. Events will be emitted only if + * there is a subscription for the event. This method does + * not add actual listeners. To listen to the event, use the + * `on` or `once` methods. The buffered events will be + * emitted before the command promise is resolved. + * + * @param {object} [params] Parameters for the subscription. + * @param {null|Array.<(Context)>} [params.contexts] The + * optional contexts parameter specifies which browsing + * contexts to subscribe to the event on. It should be + * either an array of Context objects, or null. If null, the + * event will be subscribed to globally. If omitted, the + * event will be subscribed to on the current browsing + * context. + * @returns {Promise<(function(): Promise)>} Callback + * for unsubscribing from the created subscription. + */ + subscribe: async function(params = {}) { + assertBidiIsEnabled(); + return window.test_driver_internal.bidi.bluetooth + .characteristic_event_generated.subscribe(params); + }, + /** + * Adds an event listener for the event. + * + * @param {function(CharacteristicEventGenerated): void} callback The + * callback to be called when the event is emitted. The + * callback is called with the event object as a parameter. + * @returns {function(): void} A function that removes the + * added event listener when called. + */ + on: function(callback) { + assertBidiIsEnabled(); + return window.test_driver_internal.bidi.bluetooth + .characteristic_event_generated.on(callback); + }, + /** + * Adds an event listener for the event that is only called + * once and removed afterward. + * + * @return {Promise} The promise which + * is resolved with the event object when the event is emitted. + */ + once: function() { + assertBidiIsEnabled(); + return new Promise(resolve => { + const remove_handler = + window.test_driver_internal.bidi.bluetooth + .characteristic_event_generated.on(event => { + resolve(event); + remove_handler(); + }); + }); + }, + }, + /** + * `bluetooth.DescriptorEventGeneratedParameters `_ + * event. + */ + descriptor_event_generated: { + /** + * @typedef {object} DescriptorEventGenerated + * `bluetooth.DescriptorEventGenerated `_ + * event. + */ + + /** + * Subscribes to the event. Events will be emitted only if + * there is a subscription for the event. This method does + * not add actual listeners. To listen to the event, use the + * `on` or `once` methods. The buffered events will be + * emitted before the command promise is resolved. + * + * @param {object} [params] Parameters for the subscription. + * @param {null|Array.<(Context)>} [params.contexts] The + * optional contexts parameter specifies which browsing + * contexts to subscribe to the event on. It should be + * either an array of Context objects, or null. If null, the + * event will be subscribed to globally. If omitted, the + * event will be subscribed to on the current browsing + * context. + * @returns {Promise<(function(): Promise)>} Callback + * for unsubscribing from the created subscription. + */ + subscribe: async function(params = {}) { + assertBidiIsEnabled(); + return window.test_driver_internal.bidi.bluetooth + .descriptor_event_generated.subscribe(params); + }, + /** + * Adds an event listener for the event. + * + * @param {function(DescriptorEventGenerated): void} callback The + * callback to be called when the event is emitted. The + * callback is called with the event object as a parameter. + * @returns {function(): void} A function that removes the + * added event listener when called. + */ + on: function(callback) { + assertBidiIsEnabled(); + return window.test_driver_internal.bidi.bluetooth + .descriptor_event_generated.on(callback); + }, + /** + * Adds an event listener for the event that is only called + * once and removed afterward. + * + * @return {Promise} The promise which + * is resolved with the event object when the event is emitted. + */ + once: function() { + assertBidiIsEnabled(); + return new Promise(resolve => { + const remove_handler = + window.test_driver_internal.bidi.bluetooth + .descriptor_event_generated.on(event => { + resolve(event); + remove_handler(); + }); + }); + }, + } + }, + /** + * `emulation `_ module. + */ + emulation: { + /** + * Overrides the geolocation coordinates for the specified + * browsing contexts. + * Matches the `emulation.setGeolocationOverride + * `_ + * WebDriver BiDi command. + * + * @example + * await test_driver.bidi.emulation.set_geolocation_override({ + * coordinates: { + * latitude: 52.51, + * longitude: 13.39, + * accuracy: 0.5, + * altitude: 34, + * altitudeAccuracy: 0.75, + * heading: 180, + * speed: 2.77 + * } + * }); + * + * @param {object} params - Parameters for the command. + * @param {null|object} params.coordinates - The optional + * geolocation coordinates to set. Matches the + * `emulation.GeolocationCoordinates `_ + * value. If null or omitted and the `params.error` is set, the + * emulation will be removed. Mutually exclusive with + * `params.error`. + * @param {object} params.error - The optional + * geolocation error to emulate. Matches the + * `emulation.GeolocationPositionError `_ + * value. Mutually exclusive with `params.coordinates`. + * @param {null|Array.<(Context)>} [params.contexts] The + * optional contexts parameter specifies which browsing contexts + * to set the geolocation override on. It should be either an + * array of Context objects (window or browsing context id), or + * null. If null or omitted, the override will be set on the + * current browsing context. + * @returns {Promise} Resolves when the geolocation + * override is successfully set. + */ + set_geolocation_override: function (params) { + // Ensure the bidi feature is enabled before calling the internal method + assertBidiIsEnabled(); + return window.test_driver_internal.bidi.emulation.set_geolocation_override( + params); + }, + /** + * Overrides the locale for the specified browsing contexts. + * Matches the `emulation.setLocaleOverride + * `_ + * WebDriver BiDi command. + * + * @example + * await test_driver.bidi.emulation.set_locale_override({ + * locale: 'de-DE' + * }); + * + * @param {object} params - Parameters for the command. + * @param {null|string} params.locale - The optional + * locale to set. + * @param {null|Array.<(Context)>} [params.contexts] The + * optional contexts parameter specifies which browsing contexts + * to set the locale override on. It should be either an array + * of Context objects (window or browsing context id), or null. + * If null or omitted, the override will be set on the current + * browsing context. + * @returns {Promise} Resolves when the locale override + * is successfully set. + */ + set_locale_override: function (params) { + assertBidiIsEnabled(); + return window.test_driver_internal.bidi.emulation.set_locale_override( + params); + }, + /** + * Overrides the screen orientation for the specified browsing + * contexts. + * Matches the `emulation.setScreenOrientationOverride + * `_ + * WebDriver BiDi command. + * + * @example + * await test_driver.bidi.emulation.set_screen_orientation_override({ + * screenOrientation: { + * natural: 'portrait', + * type: 'landscape-secondary' + * } + * }); + * + * @param {object} params - Parameters for the command. + * @param {null|object} params.screenOrientation - The optional + * screen orientation. Matches the + * `emulation.ScreenOrientation `_ + * type. If null or omitted, the override will be removed. + * @param {null|Array.<(Context)>} [params.contexts] The + * optional contexts parameter specifies which browsing contexts + * to set the screen orientation override on. It should be + * either an array of Context objects (window or browsing + * context id), or null. If null or omitted, the override will + * be set on the current browsing context. + * @returns {Promise} Resolves when the screen orientation + * override is successfully set. + */ + set_screen_orientation_override: function (params) { + // Ensure the bidi feature is enabled before calling the internal method + assertBidiIsEnabled(); + return window.test_driver_internal.bidi.emulation.set_screen_orientation_override( + params); + }, + }, + /** + * `log `_ module. + */ + log: { + entry_added: { + /** + * @typedef {object} LogEntryAdded `log.entryAdded `_ event. + */ + + /** + * Subscribes to the event. Events will be emitted only if + * there is a subscription for the event. This method does + * not add actual listeners. To listen to the event, use the + * `on` or `once` methods. The buffered events will be + * emitted before the command promise is resolved. + * + * @param {object} [params] Parameters for the subscription. + * @param {null|Array.<(Context)>} [params.contexts] The + * optional contexts parameter specifies which browsing + * contexts to subscribe to the event on. It should be + * either an array of Context objects, or null. If null, the + * event will be subscribed to globally. If omitted, the + * event will be subscribed to on the current browsing + * context. + * @returns {Promise<(function(): Promise)>} Callback + * for unsubscribing from the created subscription. + */ + subscribe: async function (params = {}) { + assertBidiIsEnabled(); + return window.test_driver_internal.bidi.log.entry_added.subscribe(params); + }, + /** + * Adds an event listener for the event. + * + * @param {function(LogEntryAdded): void} callback The + * callback to be called when the event is emitted. The + * callback is called with the event object as a parameter. + * @returns {function(): void} A function that removes the + * added event listener when called. + */ + on: function (callback) { + assertBidiIsEnabled(); + return window.test_driver_internal.bidi.log.entry_added.on(callback); + }, + /** + * Adds an event listener for the event that is only called + * once and removed afterward. + * + * @return {Promise} The promise which is resolved + * with the event object when the event is emitted. + */ + once: function () { + assertBidiIsEnabled(); + return new Promise(resolve => { + const remove_handler = window.test_driver_internal.bidi.log.entry_added.on( + event => { + resolve(event); + remove_handler(); + }); + }); + }, + } + }, + /** + * `permissions `_ module. + */ + permissions: { + /** + * Sets the state of a permission + * + * This function causes permission requests and queries for the status + * of a certain permission type (e.g. "push", or "background-fetch") to + * always return ``state`` for the specific origin. + * + * Matches the `permissions.setPermission `_ + * WebDriver BiDi command. + * + * @example + * await test_driver.bidi.permissions.set_permission({ + * {name: "geolocation"}, + * state: "granted", + * }); + * + * @param {object} params - Parameters for the command. + * @param {PermissionDescriptor} params.descriptor - a `PermissionDescriptor + * `_ + * or derived object. + * @param {PermissionState} params.state - a `PermissionState + * `_ + * value. + * @param {string} [params.origin] - an optional `origin` string to set the + * permission for. If omitted, the permission is set for the + * current window's origin. + * @returns {Promise} fulfilled after the permission is set, or rejected if setting + * the permission fails. + */ + set_permission: function (params) { + assertBidiIsEnabled(); + return window.test_driver_internal.bidi.permissions.set_permission( + params); + } + } + }, + /** * Set the context in which testharness.js is loaded * @@ -112,8 +1050,8 @@ let wait_click = new Promise(resolve => button.addEventListener("click", resolve)); return test_driver.click(button) - .then(wait_click) - .then(function() { + .then(() => wait_click) + .then(() => { button.remove(); if (typeof action === "function") { @@ -124,11 +1062,10 @@ }, /** - * Triggers a user-initiated click + * Triggers a user-initiated mouse click. * - * If ``element`` isn't inside the - * viewport, it will be scrolled into view before the click - * occurs. + * If ``element`` isn't inside the viewport, it will be + * scrolled into view before the click occurs. * * If ``element`` is from a different browsing context, the * command will be run in that context. @@ -322,7 +1259,7 @@ /** * Minimizes the browser window. * - * Matches the the behaviour of the `Minimize + * Matches the behaviour of the `Minimize * `_ * WebDriver command * @@ -357,6 +1294,25 @@ return window.test_driver_internal.set_window_rect(rect, context); }, + /** + * Gets a rect with the size and position on the screen from the current window state. + * + * Matches the behaviour of the `Get Window Rect + * `_ + * WebDriver command + * + * @param {WindowProxy} context - Browsing context in which + * to run the call, or null for the current + * browsing context. + * + * @returns {Promise} fulfilled after the window rect is returned, or rejected + * in cases the WebDriver command returns errors. Returns a + * `WindowRect `_ + */ + get_window_rect: function(context=null) { + return window.test_driver_internal.get_window_rect(context); + }, + /** * Send a sequence of actions * @@ -647,7 +1603,7 @@ * * This function places `Secure Payment * Confirmation `_ into - * an automated 'autoaccept' or 'autoreject' mode, to allow testing + * an automated 'autoAccept' or 'autoReject' mode, to allow testing * without user interaction with the transaction UX prompt. * * Matches the `Set SPC Transaction Mode @@ -667,8 +1623,8 @@ * @param {String} mode - The `transaction mode * `_ * to set. Must be one of "``none``", - * "``autoaccept``", or - * "``autoreject``". + * "``autoAccept``", or + * "``autoReject``". * @param {WindowProxy} context - Browsing context in which * to run the call, or null for the current * browsing context. @@ -680,6 +1636,42 @@ return window.test_driver_internal.set_spc_transaction_mode(mode, context); }, + /** + * Sets the current registration automation mode for Register Protocol Handlers. + * + * This function places `Register Protocol Handlers + * `_ into + * an automated 'autoAccept' or 'autoReject' mode, to allow testing + * without user interaction with the transaction UX prompt. + * + * Matches the `Set Register Protocol Handler Mode + * `_ + * WebDriver command. + * + * @example + * await test_driver.set_rph_registration_mode("autoAccept"); + * test.add_cleanup(() => { + * return test_driver.set_rph_registration_mode("none"); + * }); + * + * navigator.registerProtocolHandler('web+soup', 'soup?url=%s'); + * + * @param {String} mode - The `registration mode + * `_ + * to set. Must be one of "``none``", + * "``autoAccept``", or + * "``autoReject``". + * @param {WindowProxy} context - Browsing context in which + * to run the call, or null for the current + * browsing context. + * + * @returns {Promise} Fulfilled after the transaction mode has been set, + * or rejected if setting the mode fails. + */ + set_rph_registration_mode: function(mode, context=null) { + return window.test_driver_internal.set_rph_registration_mode(mode, context); + }, + /** * Cancels the Federated Credential Management dialog * @@ -968,6 +1960,215 @@ */ get_virtual_sensor_information: function(sensor_type, context=null) { return window.test_driver_internal.get_virtual_sensor_information(sensor_type, context); + }, + + /** + * Overrides device posture set by hardware. + * + * Matches the `Set device posture + * `_ + * WebDriver command. + * + * @param {String} posture - A `DevicePostureType + * `_ + * either "continuous" or "folded". + * @param {WindowProxy} [context=null] - Browsing context in which to + * run the call, or null for the + * current browsing context. + * + * @returns {Promise} Fulfilled when device posture is set. + * Rejected in case the WebDriver command errors out + * (including if a device posture of the given type + * does not exist). + */ + set_device_posture: function(posture, context=null) { + return window.test_driver_internal.set_device_posture(posture, context); + }, + + /** + * Removes device posture override and returns device posture control + * back to hardware. + * + * Matches the `Clear device posture + * `_ + * WebDriver command. + * + * @param {WindowProxy} [context=null] - Browsing context in which to + * run the call, or null for the + * current browsing context. + * + * @returns {Promise} Fulfilled after the device posture override has + * been removed. Rejected in case the WebDriver + * command errors out. + */ + clear_device_posture: function(context=null) { + return window.test_driver_internal.clear_device_posture(context); + }, + + /** + * Runs the `bounce tracking timer algorithm + * `_, + * which removes all hosts from the stateful bounce tracking map, without + * regard for the bounce tracking grace period and returns a list of the + * deleted hosts. + * + * Matches the `Run Bounce Tracking Mitigations + * `_ + * WebDriver command. + * + * @param {WindowProxy} [context=null] - Browsing context in which to + * run the call, or null for the + * current browsing context. + * @returns {Promise} Fulfilled after the bounce tracking timer + * algorithm has finished running. Returns an array + * of all hosts that were in the stateful bounce + * tracking map before deletion occurred. + */ + run_bounce_tracking_mitigations: function (context = null) { + return window.test_driver_internal.run_bounce_tracking_mitigations(context); + }, + + /** + * Creates a virtual pressure source. + * + * Matches the `Create virtual pressure source + * `_ + * WebDriver command. + * + * @param {String} source_type - A `virtual pressure source type + * `_ + * such as "cpu". + * @param {Object} [metadata={}] - Optional parameters described + * in `Create virtual pressure source + * `_. + * @param {WindowProxy} [context=null] - Browsing context in which to + * run the call, or null for the + * current browsing context. + * + * @returns {Promise} Fulfilled when virtual pressure source is created. + * Rejected in case the WebDriver command errors out + * (including if a virtual pressure source of the + * same type already exists). + */ + create_virtual_pressure_source: function(source_type, metadata={}, context=null) { + return window.test_driver_internal.create_virtual_pressure_source(source_type, metadata, context); + }, + + /** + * Causes a virtual pressure source to report a new reading. + * + * Matches the `Update virtual pressure source + * `_ + * WebDriver command. + * + * @param {String} source_type - A `virtual pressure source type + * `_ + * such as "cpu". + * @param {String} sample - A `virtual pressure state + * `_ + * such as "critical". + * @param {number} own_contribution_estimate - Optional, A `virtual own contribution estimate` + * `_ + * @param {WindowProxy} [context=null] - Browsing context in which to + * run the call, or null for the + * current browsing context. + * + * @returns {Promise} Fulfilled after the reading update reaches the + * virtual pressure source. Rejected in case the + * WebDriver command errors out (including if a + * virtual pressure source of the given type does not + * exist). + */ + update_virtual_pressure_source: function(source_type, sample, own_contribution_estimate, context=null) { + return window.test_driver_internal.update_virtual_pressure_source(source_type, sample, own_contribution_estimate, context); + }, + + /** + * Removes created virtual pressure source. + * + * Matches the `Delete virtual pressure source + * `_ + * WebDriver command. + * + * @param {String} source_type - A `virtual pressure source type + * `_ + * such as "cpu". + * @param {WindowProxy} [context=null] - Browsing context in which to + * run the call, or null for the + * current browsing context. + * + * @returns {Promise} Fulfilled after the virtual pressure source has + * been removed or if a pressure source of the given + * type does not exist. Rejected in case the + * WebDriver command errors out. + */ + remove_virtual_pressure_source: function(source_type, context=null) { + return window.test_driver_internal.remove_virtual_pressure_source(source_type, context); + }, + + /** + * Sets which hashes are considered k-anonymous for the Protected + * Audience interest group with specified `owner` and `name`. + * + * Matches the `Set Protected Audience K-Anonymity + * + * WebDriver command. + * + * @param {String} owner - Origin of the owner of the interest group + * to modify + * @param {String} name - Name of the interest group to modify + * @param {Array} hashes - An array of strings, each of which is a + * base64 ecoded hash to consider k-anonymous. + * + * @returns {Promise} Fulfilled after the k-anonymity status for the + * specified Protected Audience interest group has + * been updated. + * + */ + set_protected_audience_k_anonymity: function(owner, name, hashes, context = null) { + return window.test_driver_internal.set_protected_audience_k_anonymity(owner, name, hashes, context); + }, + + /** + * Overrides the display features provided by the hardware so the viewport segments + * can be emulated. + * + * Matches the `Set display features + * `_ + * WebDriver command. + * + * @param {Array} features - An array of `DisplayFeatureOverride + * `. + * @param {WindowProxy} [context=null] - Browsing context in which to + * run the call, or null for the + * current browsing context. + * + * @returns {Promise} Fulfilled when the display features are set. + * Rejected in case the WebDriver command errors out + * (including if the array is malformed). + */ + set_display_features: function(features, context=null) { + return window.test_driver_internal.set_display_features(features, context); + }, + + /** + * Removes display features override and returns the control + * back to hardware. + * + * Matches the `Clear display features + * `_ + * WebDriver command. + * + * @param {WindowProxy} [context=null] - Browsing context in which to + * run the call, or null for the + * current browsing context. + * + * @returns {Promise} Fulfilled after the display features override has + * been removed. Rejected in case the WebDriver + * command errors out. + */ + clear_display_features: function(context=null) { + return window.test_driver_internal.clear_display_features(context); } }; @@ -980,6 +2181,99 @@ */ in_automation: false, + bidi: { + bluetooth: { + handle_request_device_prompt: function() { + throw new Error( + 'bidi.bluetooth.handle_request_device_prompt is not implemented by testdriver-vendor.js'); + }, + simulate_adapter: function () { + throw new Error( + "bidi.bluetooth.simulate_adapter is not implemented by testdriver-vendor.js"); + }, + disable_simulation: function () { + throw new Error( + "bidi.bluetooth.disable_simulation is not implemented by testdriver-vendor.js"); + }, + simulate_preconnected_peripheral: function() { + throw new Error( + 'bidi.bluetooth.simulate_preconnected_peripheral is not implemented by testdriver-vendor.js'); + }, + request_device_prompt_updated: { + async subscribe() { + throw new Error( + 'bidi.bluetooth.request_device_prompt_updated.subscribe is not implemented by testdriver-vendor.js'); + }, + on() { + throw new Error( + 'bidi.bluetooth.request_device_prompt_updated.on is not implemented by testdriver-vendor.js'); + } + }, + gatt_connection_attempted: { + async subscribe() { + throw new Error( + 'bidi.bluetooth.gatt_connection_attempted.subscribe is not implemented by testdriver-vendor.js'); + }, + on() { + throw new Error( + 'bidi.bluetooth.gatt_connection_attempted.on is not implemented by testdriver-vendor.js'); + } + }, + characteristic_event_generated: { + async subscribe() { + throw new Error( + 'bidi.bluetooth.characteristic_event_generated.subscribe is not implemented by testdriver-vendor.js'); + }, + on() { + throw new Error( + 'bidi.bluetooth.characteristic_event_generated.on is not implemented by testdriver-vendor.js'); + } + }, + descriptor_event_generated: { + async subscribe() { + throw new Error( + 'bidi.bluetooth.descriptor_event_generated.subscribe is not implemented by testdriver-vendor.js'); + }, + on() { + throw new Error( + 'bidi.bluetooth.descriptor_event_generated.on is not implemented by testdriver-vendor.js'); + } + } + }, + emulation: { + set_geolocation_override: function (params) { + throw new Error( + "bidi.emulation.set_geolocation_override is not implemented by testdriver-vendor.js"); + }, + set_locale_override: function (params) { + throw new Error( + "bidi.emulation.set_locale_override is not implemented by testdriver-vendor.js"); + }, + set_screen_orientation_override: function (params) { + throw new Error( + "bidi.emulation.set_screen_orientation_override is not implemented by testdriver-vendor.js"); + } + }, + log: { + entry_added: { + async subscribe() { + throw new Error( + "bidi.log.entry_added.subscribe is not implemented by testdriver-vendor.js"); + }, + on() { + throw new Error( + "bidi.log.entry_added.on is not implemented by testdriver-vendor.js"); + } + } + }, + permissions: { + async set_permission() { + throw new Error( + "bidi.permissions.set_permission() is not implemented by testdriver-vendor.js"); + } + } + }, + async click(element, coords) { if (this.in_automation) { throw new Error("click() is not implemented by testdriver-vendor.js"); @@ -1002,6 +2296,14 @@ throw new Error("get_named_cookie() is not implemented by testdriver-vendor.js"); }, + async get_computed_role(element) { + throw new Error("get_computed_role is a testdriver.js function which cannot be run in this context."); + }, + + async get_computed_name(element) { + throw new Error("get_computed_name is a testdriver.js function which cannot be run in this context."); + }, + async send_keys(element, keys) { if (this.in_automation) { throw new Error("send_keys() is not implemented by testdriver-vendor.js"); @@ -1046,6 +2348,10 @@ throw new Error("set_window_rect() is not implemented by testdriver-vendor.js"); }, + async get_window_rect(context=null) { + throw new Error("get_window_rect() is not implemented by testdriver-vendor.js"); + }, + async action_sequence(actions, context=null) { throw new Error("action_sequence() is not implemented by testdriver-vendor.js"); }, @@ -1094,6 +2400,10 @@ throw new Error("set_spc_transaction_mode() is not implemented by testdriver-vendor.js"); }, + set_rph_registration_mode: function(mode, context=null) { + return Promise.reject(new Error("unimplemented")); + }, + async cancel_fedcm_dialog(context=null) { throw new Error("cancel_fedcm_dialog() is not implemented by testdriver-vendor.js"); }, @@ -1140,6 +2450,42 @@ async get_virtual_sensor_information(sensor_type, context=null) { throw new Error("get_virtual_sensor_information() is not implemented by testdriver-vendor.js"); + }, + + async set_device_posture(posture, context=null) { + throw new Error("set_device_posture() is not implemented by testdriver-vendor.js"); + }, + + async clear_device_posture(context=null) { + throw new Error("clear_device_posture() is not implemented by testdriver-vendor.js"); + }, + + async run_bounce_tracking_mitigations(context=null) { + throw new Error("run_bounce_tracking_mitigations() is not implemented by testdriver-vendor.js"); + }, + + async create_virtual_pressure_source(source_type, metadata={}, context=null) { + throw new Error("create_virtual_pressure_source() is not implemented by testdriver-vendor.js"); + }, + + async update_virtual_pressure_source(source_type, sample, own_contribution_estimate, context=null) { + throw new Error("update_virtual_pressure_source() is not implemented by testdriver-vendor.js"); + }, + + async remove_virtual_pressure_source(source_type, context=null) { + throw new Error("remove_virtual_pressure_source() is not implemented by testdriver-vendor.js"); + }, + + async set_protected_audience_k_anonymity(owner, name, hashes, context=null) { + throw new Error("set_protected_audience_k_anonymity() is not implemented by testdriver-vendor.js"); + }, + + async set_display_features(features, context=null) { + throw new Error("set_display_features() is not implemented by testdriver-vendor.js"); + }, + + async clear_display_features(context=null) { + throw new Error("clear_display_features() is not implemented by testdriver-vendor.js"); } }; })(); diff --git a/test/fixtures/wpt/resources/testharness-shadowrealm-audioworkletprocessor.js b/test/fixtures/wpt/resources/testharness-shadowrealm-audioworkletprocessor.js new file mode 100644 index 00000000000..a87d9130908 --- /dev/null +++ b/test/fixtures/wpt/resources/testharness-shadowrealm-audioworkletprocessor.js @@ -0,0 +1,52 @@ +/** + * AudioWorkletProcessor intended for hosting a ShadowRealm and running a test + * inside of that ShadowRealm. + */ +globalThis.TestRunner = class TestRunner extends AudioWorkletProcessor { + constructor() { + super(); + this.createShadowRealmAndStartTests(); + } + + /** + * Fetch adaptor function intended as a drop-in replacement for fetchAdaptor() + * (see testharness-shadowrealm-outer.js), but it does not assume fetch() is + * present in the realm. Instead, it relies on setupFakeFetchOverMessagePort() + * having been called on the port on the other side of this.port's channel. + */ + fetchOverPortExecutor(resource) { + return (resolve, reject) => { + const listener = (event) => { + if (typeof event.data !== "string" || !event.data.startsWith("fetchResult::")) { + return; + } + + const result = event.data.slice("fetchResult::".length); + if (result.startsWith("success::")) { + resolve(result.slice("success::".length)); + } else { + reject(result.slice("fail::".length)); + } + + this.port.removeEventListener("message", listener); + } + this.port.addEventListener("message", listener); + this.port.start(); + this.port.postMessage(`fetchRequest::${resource}`); + } + } + + /** + * Async method, which is patched over in + * (test).any.audioworklet-shadowrealm.js; see serve.py + */ + async createShadowRealmAndStartTests() { + throw new Error("Forgot to overwrite this method!"); + } + + /** Overrides AudioWorkletProcessor.prototype.process() */ + process() { + return false; + } +}; +registerProcessor("test-runner", TestRunner); diff --git a/test/fixtures/wpt/resources/testharness-shadowrealm-inner.js b/test/fixtures/wpt/resources/testharness-shadowrealm-inner.js new file mode 100644 index 00000000000..a9bdf9fc76c --- /dev/null +++ b/test/fixtures/wpt/resources/testharness-shadowrealm-inner.js @@ -0,0 +1,38 @@ +// testharness file with ShadowRealm utilities to be imported inside ShadowRealm + +/** + * Set up all properties on the ShadowRealm's global object that tests will + * expect to be present. + * + * @param {string} queryString - string to use as value for location.search, + * used for subsetting some tests + * @param {function} fetchAdaptor - a function that takes a resource URI and + * returns a function which itself takes a (resolve, reject) pair from the + * hosting realm, and calls resolve with the text result of fetching the + * resource, or reject with a string indicating the error that occurred + */ +globalThis.setShadowRealmGlobalProperties = function (queryString, fetchAdaptor) { + globalThis.fetch_json = (resource) => { + const executor = fetchAdaptor(resource); + return new Promise(executor).then((s) => JSON.parse(s)); + }; + + // Used only by idlharness.js + globalThis.fetch_spec = (spec) => { + const resource = `/interfaces/${spec}.idl`; + const executor = fetchAdaptor(resource); + return new Promise(executor).then( + idl => ({ spec, idl }), + () => { + throw new IdlHarnessError(`Error fetching ${resource}.`); + }); + } + + globalThis.location = { search: queryString }; +}; + +globalThis.GLOBAL = { + isWindow: function() { return false; }, + isWorker: function() { return false; }, + isShadowRealm: function() { return true; }, +}; diff --git a/test/fixtures/wpt/resources/testharness-shadowrealm-outer.js b/test/fixtures/wpt/resources/testharness-shadowrealm-outer.js new file mode 100644 index 00000000000..1affa72c2c5 --- /dev/null +++ b/test/fixtures/wpt/resources/testharness-shadowrealm-outer.js @@ -0,0 +1,151 @@ +// testharness file with ShadowRealm utilities to be imported in the realm +// hosting the ShadowRealm + +/** + * Convenience function for evaluating some async code in the ShadowRealm and + * waiting for the result. + * + * In case of error, this function intentionally exposes the stack trace (if it + * is available) to the hosting realm, for debugging purposes. + * + * @param {ShadowRealm} realm - the ShadowRealm to evaluate the code in + * @param {string} asyncBody - the code to evaluate; will be put in the body of + * an async function, and must return a value explicitly if a value is to be + * returned to the hosting realm. + */ +globalThis.shadowRealmEvalAsync = function (realm, asyncBody) { + return new Promise(realm.evaluate(` + (resolve, reject) => { + (async () => { + ${asyncBody} + })().then(resolve, (e) => reject(e.toString() + "\\n" + (e.stack || ""))); + } + `)); +}; + +/** + * Convenience adaptor function for fetch() that can be passed to + * setShadowRealmGlobalProperties() (see testharness-shadowrealm-inner.js). + * Used to adapt the hosting realm's fetch(), if present, to fetch a resource + * and pass its text through the callable boundary to the ShadowRealm. + */ +globalThis.fetchAdaptor = (resource) => (resolve, reject) => { + fetch(resource) + .then(res => res.text()) + .then(resolve, (e) => reject(e.toString())); +}; + +let workerMessagePortPromise; +/** + * Used when the hosting realm is a worker. This value is a Promise that + * resolves to a function that posts a message to the worker's message port, + * just like postMessage(). The message port is only available asynchronously in + * SharedWorkers and ServiceWorkers. + */ +globalThis.getPostMessageFunc = async function () { + if (typeof postMessage === "function") { + return postMessage; // postMessage available directly in dedicated worker + } + + if (workerMessagePortPromise) { + return await workerMessagePortPromise; + } + + throw new Error("getPostMessageFunc is intended for Worker scopes"); +} + +// Port available asynchronously in shared worker, but not via an async func +let savedResolver; +if (globalThis.constructor.name === "SharedWorkerGlobalScope") { + workerMessagePortPromise = new Promise((resolve) => { + savedResolver = resolve; + }); + addEventListener("connect", function (event) { + const port = event.ports[0]; + savedResolver(port.postMessage.bind(port)); + }); +} else if (globalThis.constructor.name === "ServiceWorkerGlobalScope") { + workerMessagePortPromise = new Promise((resolve) => { + savedResolver = resolve; + }); + addEventListener("message", (e) => { + if (typeof e.data === "object" && e.data !== null && e.data.type === "connect") { + const client = e.source; + savedResolver(client.postMessage.bind(client)); + } + }); +} + +/** + * Used when the hosting realm does not permit dynamic import, e.g. in + * ServiceWorkers or AudioWorklets. Requires an adaptor function such as + * fetchAdaptor() above, or an equivalent if fetch() is not present in the + * hosting realm. + * + * @param {ShadowRealm} realm - the ShadowRealm in which to setup a + * fakeDynamicImport() global function. + * @param {function} adaptor - an adaptor function that does what fetchAdaptor() + * does. + */ +globalThis.setupFakeDynamicImportInShadowRealm = function(realm, adaptor) { + function fetchModuleTextExecutor(url) { + return (resolve, reject) => { + new Promise(adaptor(url)) + .then(text => realm.evaluate(text + ";\nundefined")) + .then(resolve, (e) => reject(e.toString())); + } + } + + realm.evaluate(` + (fetchModuleTextExecutor) => { + globalThis.fakeDynamicImport = function (url) { + return new Promise(fetchModuleTextExecutor(url)); + } + } + `)(fetchModuleTextExecutor); +}; + +/** + * Used when the hosting realm does not expose fetch(), i.e. in worklets. The + * port on the other side of the channel needs to send messages starting with + * 'fetchRequest::' and listen for messages starting with 'fetchResult::'. See + * testharness-shadowrealm-audioworkletprocessor.js. + * + * @param {port} MessagePort - the message port on which to listen for fetch + * requests + */ +globalThis.setupFakeFetchOverMessagePort = function (port) { + port.addEventListener("message", (event) => { + if (typeof event.data !== "string" || !event.data.startsWith("fetchRequest::")) { + return; + } + + fetch(event.data.slice("fetchRequest::".length)) + .then(res => res.text()) + .then( + text => port.postMessage(`fetchResult::success::${text}`), + error => port.postMessage(`fetchResult::fail::${error}`), + ); + }); + port.start(); +} + +/** + * Returns a message suitable for posting with postMessage() that will signal to + * the test harness that the tests are finished and there was an error in the + * setup code. + * + * @param {message} any - error + */ +globalThis.createSetupErrorResult = function (message) { + return { + type: "complete", + tests: [], + asserts: [], + status: { + status: 1, // TestsStatus.ERROR, + message: String(message), + stack: typeof message === "object" && message !== null && "stack" in message ? message.stack : undefined, + }, + }; +}; diff --git a/test/fixtures/wpt/resources/testharness.js b/test/fixtures/wpt/resources/testharness.js index 126ae96423b..f495b62458b 100644 --- a/test/fixtures/wpt/resources/testharness.js +++ b/test/fixtures/wpt/resources/testharness.js @@ -88,10 +88,15 @@ status: harness_status.structured_clone(), asserts: asserts.map(assert => assert.structured_clone())}); }] - } + }; on_event(window, 'load', function() { + setTimeout(() => { this_obj.all_loaded = true; + if (tests.all_done()) { + tests.complete(); + } + },0); }); on_event(window, 'message', function(event) { @@ -198,7 +203,7 @@ } }); this.message_events = new_events; - } + }; WindowTestEnvironment.prototype.next_default_test_name = function() { var suffix = this.name_counter > 0 ? " " + this.name_counter : ""; @@ -220,8 +225,8 @@ WindowTestEnvironment.prototype.test_timeout = function() { var metas = document.getElementsByTagName("meta"); for (var i = 0; i < metas.length; i++) { - if (metas[i].name == "timeout") { - if (metas[i].content == "long") { + if (metas[i].name === "timeout") { + if (metas[i].content === "long") { return settings.harness_timeout.long; } break; @@ -482,7 +487,7 @@ this.all_loaded = false; this.on_loaded_callback = null; Promise.resolve().then(function() { - this.all_loaded = true + this.all_loaded = true; if (this.on_loaded_callback) { this.on_loaded_callback(); } @@ -558,7 +563,7 @@ // The worker object may be from another execution context, // so do not use instanceof here. return 'ServiceWorker' in global_scope && - Object.prototype.toString.call(worker) == '[object ServiceWorker]'; + Object.prototype.toString.call(worker) === '[object ServiceWorker]'; } var seen_func_name = Object.create(null); @@ -600,7 +605,7 @@ /** * @callback TestFunction - * @param {Test} test - The test currently being run. + * @param {Test} test - The test currnetly being run. * @param {Any[]} args - Additional args to pass to function. * */ @@ -805,7 +810,7 @@ return bring_promise_to_current_realm(promise) .then(test.unreached_func("Should have rejected: " + description)) .catch(function(e) { - assert_throws_js_impl(constructor, function() { throw e }, + assert_throws_js_impl(constructor, function() { throw e; }, description, "promise_rejects_js"); }); } @@ -852,16 +857,64 @@ promise = promiseOrConstructor; description = descriptionOrPromise; assert(maybeDescription === undefined, - "Too many args pased to no-constructor version of promise_rejects_dom"); + "Too many args passed to no-constructor version of promise_rejects_dom, or accidentally explicitly passed undefined"); } return bring_promise_to_current_realm(promise) .then(test.unreached_func("Should have rejected: " + description)) .catch(function(e) { - assert_throws_dom_impl(type, function() { throw e }, description, + assert_throws_dom_impl(type, function() { throw e; }, description, "promise_rejects_dom", constructor); }); } +/** + * Assert that a `Promise` is rejected with a `QuotaExceededError` with the + * expected values. + * + * For the remaining arguments, there are two ways of calling + * `promise_rejects_quotaexceedederror`: + * + * 1) If the `QuotaExceededError` is expected to come from the + * current global, the second argument should be the promise + * expected to reject, the third and a fourth the expected + * `requested` and `quota` property values, and the fifth, + * optional, argument is the assertion description. + * + * 2) If the `QuotaExceededError` is expected to come from some + * other global, the second argument should be the + * `QuotaExceededError` constructor from that global, the third + * argument should be the promise expected to reject, the fourth + * and fifth the expected `requested` and `quota` property + * values, and the sixth, optional, argument is the assertion + * description. + * + */ + function promise_rejects_quotaexceedederror(test, promiseOrConstructor, requestedOrPromise, quotaOrRequested, descriptionOrQuota, maybeDescription) + { + let constructor, promise, requested, quota, description; + if (typeof promiseOrConstructor === "function" && + promiseOrConstructor.name === "QuotaExceededError") { + constructor = promiseOrConstructor; + promise = requestedOrPromise; + requested = quotaOrRequested; + quota = descriptionOrQuota; + description = maybeDescription; + } else { + constructor = self.QuotaExceededError; + promise = promiseOrConstructor; + requested = requestedOrPromise; + quota = quotaOrRequested; + description = descriptionOrQuota; + assert(maybeDescription === undefined, + "Too many args passed to no-constructor version of promise_rejects_quotaexceedederror"); + } + return bring_promise_to_current_realm(promise) + .then(test.unreached_func("Should have rejected: " + description)) + .catch(function(e) { + assert_throws_quotaexceedederror_impl(function() { throw e; }, requested, quota, description, "promise_rejects_quotaexceedederror", constructor); + }); + } + /** * Assert that a Promise is rejected with the provided value. * @@ -876,7 +929,7 @@ return bring_promise_to_current_realm(promise) .then(test.unreached_func("Should have rejected: " + description)) .catch(function(e) { - assert_throws_exactly_impl(exception, function() { throw e }, + assert_throws_exactly_impl(exception, function() { throw e; }, description, "promise_rejects_exactly"); }); } @@ -902,7 +955,7 @@ */ function EventWatcher(test, watchedNode, eventTypes, timeoutPromise) { - if (typeof eventTypes == 'string') { + if (typeof eventTypes === 'string') { eventTypes = [eventTypes]; } @@ -967,7 +1020,7 @@ if (waitingFor) { return Promise.reject('Already waiting for an event or events'); } - if (typeof types == 'string') { + if (typeof types === 'string') { types = [types]; } if (options && options.record && options.record === 'all') { @@ -982,7 +1035,7 @@ // This should always fail, otherwise we should have // resolved the promise. - assert_true(waitingFor.types.length == 0, + assert_true(waitingFor.types.length === 0, 'Timed out waiting for ' + waitingFor.types.join(', ')); var result = recordedEvents; recordedEvents = null; @@ -1006,13 +1059,13 @@ /** * Stop listening for events */ - function stop_watching() { + this.stop_watching = function() { for (var i = 0; i < eventTypes.length; i++) { watchedNode.removeEventListener(eventTypes[i], eventHandler, false); } }; - test._add_cleanup(stop_watching); + test._add_cleanup(this.stop_watching); return this; } @@ -1089,7 +1142,7 @@ { if (typeof func !== "function") { tests.set_status(tests.status.ERROR, - "promise_test invoked without a function"); + "`promise_setup` invoked without a function"); tests.complete(); return; } @@ -1127,7 +1180,7 @@ * * Typically this function is called implicitly on page load; it's * only necessary for users to call this when either the - * ``explicit_done`` or ``single_page`` properties have been set + * ``explicit_done`` or ``single_test`` properties have been set * via the :js:func:`setup` function. * * For single page tests this marks the test as complete and sets its status. @@ -1196,6 +1249,23 @@ object.addEventListener(event, callback, false); } + // Internal helper function to provide timeout-like functionality in + // environments where there is no setTimeout(). (No timeout ID or + // clearTimeout().) + function fake_set_timeout(callback, delay) { + var p = Promise.resolve(); + var start = Date.now(); + var end = start + delay; + function check() { + if ((end - Date.now()) > 0) { + p.then(check); + } else { + callback(); + } + } + p.then(check); + } + /** * Global version of :js:func:`Test.step_timeout` for use in single page tests. * @@ -1207,7 +1277,8 @@ function step_timeout(func, timeout) { var outer_this = this; var args = Array.prototype.slice.call(arguments, 2); - return setTimeout(function() { + var local_set_timeout = typeof global_scope.setTimeout === "undefined" ? fake_set_timeout : setTimeout; + return local_set_timeout(function() { func.apply(outer_this, args); }, timeout * tests.timeout_multiplier); } @@ -1217,6 +1288,7 @@ expose(promise_test, 'promise_test'); expose(promise_rejects_js, 'promise_rejects_js'); expose(promise_rejects_dom, 'promise_rejects_dom'); + expose(promise_rejects_quotaexceedederror, 'promise_rejects_quotaexceedederror'); expose(promise_rejects_exactly, 'promise_rejects_exactly'); expose(generate_tests, 'generate_tests'); expose(setup, 'setup'); @@ -1307,6 +1379,15 @@ "0xffff": "uffff", }; + const formatEscapeMap = { + "\\": "\\\\", + '"': '\\"' + }; + for (const p in replacements) { + formatEscapeMap[String.fromCharCode(p)] = "\\" + replacements[p]; + } + const formatEscapePattern = new RegExp(`[${Object.keys(formatEscapeMap).map(k => k === "\\" ? "\\\\" : k).join("")}]`, "g"); + /** * Convert a value to a nice, human-readable string * @@ -1357,12 +1438,7 @@ switch (typeof val) { case "string": - val = val.replace(/\\/g, "\\\\"); - for (var p in replacements) { - var replace = "\\" + replacements[p]; - val = val.replace(RegExp(String.fromCharCode(p), "g"), replace); - } - return '"' + val.replace(/"/g, '\\"') + '"'; + return '"' + val.replace(formatEscapePattern, match => formatEscapeMap[match]) + '"'; case "boolean": case "undefined": return String(val); @@ -1373,6 +1449,8 @@ return "-0"; } return String(val); + case "bigint": + return String(val) + 'n'; case "object": if (val === null) { return "null"; @@ -1396,11 +1474,11 @@ case Node.COMMENT_NODE: return "Comment node "; case Node.DOCUMENT_NODE: - return "Document node with " + val.childNodes.length + (val.childNodes.length == 1 ? " child" : " children"); + return "Document node with " + val.childNodes.length + (val.childNodes.length === 1 ? " child" : " children"); case Node.DOCUMENT_TYPE_NODE: return "DocumentType node"; case Node.DOCUMENT_FRAGMENT_NODE: - return "DocumentFragment node with " + val.childNodes.length + (val.childNodes.length == 1 ? " child" : " children"); + return "DocumentFragment node with " + val.childNodes.length + (val.childNodes.length === 1 ? " child" : " children"); default: return "Node object of unknown type"; } @@ -1744,20 +1822,25 @@ /** * Assert that ``actual`` is a number less than ``expected``. * - * @param {number} actual - Test value. - * @param {number} expected - Number that ``actual`` must be less than. + * @param {number|bigint} actual - Test value. + * @param {number|bigint} expected - Value that ``actual`` must be less than. * @param {string} [description] - Description of the condition being tested. */ function assert_less_than(actual, expected, description) { /* - * Test if a primitive number is less than another + * Test if a primitive number (or bigint) is less than another */ - assert(typeof actual === "number", + assert(typeof actual === "number" || typeof actual === "bigint", "assert_less_than", description, "expected a number but got a ${type_actual}", {type_actual:typeof actual}); + assert(typeof actual === typeof expected, + "assert_less_than", description, + "expected a ${type_expected} but got a ${type_actual}", + {type_expected:typeof expected, type_actual:typeof actual}); + assert(actual < expected, "assert_less_than", description, "expected a number less than ${expected} but got ${actual}", @@ -1768,20 +1851,25 @@ /** * Assert that ``actual`` is a number greater than ``expected``. * - * @param {number} actual - Test value. - * @param {number} expected - Number that ``actual`` must be greater than. + * @param {number|bigint} actual - Test value. + * @param {number|bigint} expected - Value that ``actual`` must be greater than. * @param {string} [description] - Description of the condition being tested. */ function assert_greater_than(actual, expected, description) { /* - * Test if a primitive number is greater than another + * Test if a primitive number (or bigint) is greater than another */ - assert(typeof actual === "number", + assert(typeof actual === "number" || typeof actual === "bigint", "assert_greater_than", description, "expected a number but got a ${type_actual}", {type_actual:typeof actual}); + assert(typeof actual === typeof expected, + "assert_greater_than", description, + "expected a ${type_expected} but got a ${type_actual}", + {type_expected:typeof expected, type_actual:typeof actual}); + assert(actual > expected, "assert_greater_than", description, "expected a number greater than ${expected} but got ${actual}", @@ -1793,21 +1881,31 @@ * Assert that ``actual`` is a number greater than ``lower`` and less * than ``upper`` but not equal to either. * - * @param {number} actual - Test value. - * @param {number} lower - Number that ``actual`` must be greater than. - * @param {number} upper - Number that ``actual`` must be less than. + * @param {number|bigint} actual - Test value. + * @param {number|bigint} lower - Value that ``actual`` must be greater than. + * @param {number|bigint} upper - Value that ``actual`` must be less than. * @param {string} [description] - Description of the condition being tested. */ function assert_between_exclusive(actual, lower, upper, description) { /* - * Test if a primitive number is between two others + * Test if a primitive number (or bigint) is between two others */ - assert(typeof actual === "number", + assert(typeof lower === typeof upper, + "assert_between_exclusive", description, + "expected lower (${type_lower}) and upper (${type_upper}) types to match (test error)", + {type_lower:typeof lower, type_upper:typeof upper}); + + assert(typeof actual === "number" || typeof actual === "bigint", "assert_between_exclusive", description, "expected a number but got a ${type_actual}", {type_actual:typeof actual}); + assert(typeof actual === typeof lower, + "assert_between_exclusive", description, + "expected a ${type_lower} but got a ${type_actual}", + {type_lower:typeof lower, type_actual:typeof actual}); + assert(actual > lower && actual < upper, "assert_between_exclusive", description, "expected a number greater than ${lower} " + @@ -1819,21 +1917,26 @@ /** * Assert that ``actual`` is a number less than or equal to ``expected``. * - * @param {number} actual - Test value. - * @param {number} expected - Number that ``actual`` must be less + * @param {number|bigint} actual - Test value. + * @param {number|bigint} expected - Value that ``actual`` must be less * than or equal to. * @param {string} [description] - Description of the condition being tested. */ function assert_less_than_equal(actual, expected, description) { /* - * Test if a primitive number is less than or equal to another + * Test if a primitive number (or bigint) is less than or equal to another */ - assert(typeof actual === "number", + assert(typeof actual === "number" || typeof actual === "bigint", "assert_less_than_equal", description, "expected a number but got a ${type_actual}", {type_actual:typeof actual}); + assert(typeof actual === typeof expected, + "assert_less_than_equal", description, + "expected a ${type_expected} but got a ${type_actual}", + {type_expected:typeof expected, type_actual:typeof actual}); + assert(actual <= expected, "assert_less_than_equal", description, "expected a number less than or equal to ${expected} but got ${actual}", @@ -1844,21 +1947,26 @@ /** * Assert that ``actual`` is a number greater than or equal to ``expected``. * - * @param {number} actual - Test value. - * @param {number} expected - Number that ``actual`` must be greater + * @param {number|bigint} actual - Test value. + * @param {number|bigint} expected - Value that ``actual`` must be greater * than or equal to. * @param {string} [description] - Description of the condition being tested. */ function assert_greater_than_equal(actual, expected, description) { /* - * Test if a primitive number is greater than or equal to another + * Test if a primitive number (or bigint) is greater than or equal to another */ - assert(typeof actual === "number", + assert(typeof actual === "number" || typeof actual === "bigint", "assert_greater_than_equal", description, "expected a number but got a ${type_actual}", {type_actual:typeof actual}); + assert(typeof actual === typeof expected, + "assert_greater_than_equal", description, + "expected a ${type_expected} but got a ${type_actual}", + {type_expected:typeof expected, type_actual:typeof actual}); + assert(actual >= expected, "assert_greater_than_equal", description, "expected a number greater than or equal to ${expected} but got ${actual}", @@ -1870,21 +1978,31 @@ * Assert that ``actual`` is a number greater than or equal to ``lower`` and less * than or equal to ``upper``. * - * @param {number} actual - Test value. - * @param {number} lower - Number that ``actual`` must be greater than or equal to. - * @param {number} upper - Number that ``actual`` must be less than or equal to. + * @param {number|bigint} actual - Test value. + * @param {number|bigint} lower - Value that ``actual`` must be greater than or equal to. + * @param {number|bigint} upper - Value that ``actual`` must be less than or equal to. * @param {string} [description] - Description of the condition being tested. */ function assert_between_inclusive(actual, lower, upper, description) { /* - * Test if a primitive number is between to two others or equal to either of them + * Test if a primitive number (or bigint) is between to two others or equal to either of them */ - assert(typeof actual === "number", + assert(typeof lower === typeof upper, + "assert_between_inclusive", description, + "expected lower (${type_lower}) and upper (${type_upper}) types to match (test error)", + {type_lower:typeof lower, type_upper:typeof upper}); + + assert(typeof actual === "number" || typeof actual === "bigint", "assert_between_inclusive", description, "expected a number but got a ${type_actual}", {type_actual:typeof actual}); + assert(typeof actual === typeof lower, + "assert_between_inclusive", description, + "expected a ${type_lower} but got a ${type_actual}", + {type_lower:typeof lower, type_actual:typeof actual}); + assert(actual >= lower && actual <= upper, "assert_between_inclusive", description, "expected a number greater than or equal to ${lower} " + @@ -2009,30 +2127,46 @@ /** - * Assert that ``object`` has a property named ``property_name`` and that the property is readonly. + * Assert that ``object`` has a property named ``property_name`` and that the property is not writable or has no setter. * - * Note: The implementation tries to update the named property, so - * any side effects of updating will be triggered. Users are - * encouraged to instead inspect the property descriptor of ``property_name`` on ``object``. - * - * @param {Object} object - Object that should have the given property in its prototype chain. + * @param {Object} object - Object that should have the given (not necessarily own) property. * @param {string} property_name - Expected property name. * @param {string} [description] - Description of the condition being tested. */ function assert_readonly(object, property_name, description) { - var initial_value = object[property_name]; - try { - //Note that this can have side effects in the case where - //the property has PutForwards - object[property_name] = initial_value + "a"; //XXX use some other value here? - assert(same_value(object[property_name], initial_value), - "assert_readonly", description, - "changing property ${p} succeeded", - {p:property_name}); - } finally { - object[property_name] = initial_value; - } + assert(property_name in object, + "assert_readonly", description, + "property ${p} not found", + {p:property_name}); + + let desc; + while (object && (desc = Object.getOwnPropertyDescriptor(object, property_name)) === undefined) { + object = Object.getPrototypeOf(object); + } + + assert(desc !== undefined, + "assert_readonly", description, + "could not find a descriptor for property ${p}", + {p:property_name}); + + if (desc.hasOwnProperty("value")) { + // We're a data property descriptor + assert(desc.writable === false, "assert_readonly", description, + "descriptor [[Writable]] expected false got ${actual}", {actual:desc.writable}); + } else if (desc.hasOwnProperty("get") || desc.hasOwnProperty("set")) { + // We're an accessor property descriptor + assert(desc.set === undefined, "assert_readonly", description, + "property ${p} is an accessor property with a [[Set]] attribute, cannot test readonly-ness", + {p:property_name}); + } else { + // We're a generic property descriptor + // This shouldn't happen, because Object.getOwnPropertyDescriptor + // forwards the return value of [[GetOwnProperty]] (P), which must + // be a fully populated Property Descriptor or Undefined. + assert(false, "assert_readonly", description, + "Object.getOwnPropertyDescriptor must return a fully populated property descriptor"); + } } expose_assert(assert_readonly, "assert_readonly"); @@ -2078,7 +2212,7 @@ {func:func}); // Basic sanity-check on the passed-in constructor - assert(typeof constructor == "function", + assert(typeof constructor === "function", assertion_type, description, "${constructor} is not a constructor", {constructor:constructor}); @@ -2151,9 +2285,9 @@ func = funcOrConstructor; description = descriptionOrFunc; assert(maybeDescription === undefined, - "Too many args pased to no-constructor version of assert_throws_dom"); + "Too many args passed to no-constructor version of assert_throws_dom, or accidentally explicitly passed undefined"); } - assert_throws_dom_impl(type, func, description, "assert_throws_dom", constructor) + assert_throws_dom_impl(type, func, description, "assert_throws_dom", constructor); } expose_assert(assert_throws_dom, "assert_throws_dom"); @@ -2186,8 +2320,8 @@ {func:func}); // Sanity-check our type - assert(typeof type == "number" || - typeof type == "string", + assert(typeof type === "number" || + typeof type === "string", assertion_type, description, "${type} is not a number or string", {type:type}); @@ -2211,7 +2345,6 @@ NETWORK_ERR: 'NetworkError', ABORT_ERR: 'AbortError', URL_MISMATCH_ERR: 'URLMismatchError', - QUOTA_EXCEEDED_ERR: 'QuotaExceededError', TIMEOUT_ERR: 'TimeoutError', INVALID_NODE_TYPE_ERR: 'InvalidNodeTypeError', DATA_CLONE_ERR: 'DataCloneError' @@ -2236,7 +2369,6 @@ NetworkError: 19, AbortError: 20, URLMismatchError: 21, - QuotaExceededError: 22, TimeoutError: 23, InvalidNodeTypeError: 24, DataCloneError: 25, @@ -2267,12 +2399,19 @@ if (typeof type === "number") { if (type === 0) { throw new AssertionError('Test bug: ambiguous DOMException code 0 passed to assert_throws_dom()'); - } else if (!(type in code_name_map)) { + } + if (type === 22) { + throw new AssertionError('Test bug: QuotaExceededError needs to be tested for using assert_throws_quotaexceedederror()'); + } + if (!(type in code_name_map)) { throw new AssertionError('Test bug: unrecognized DOMException code "' + type + '" passed to assert_throws_dom()'); } name = code_name_map[type]; required_props.code = type; } else if (typeof type === "string") { + if (name === "QuotaExceededError") { + throw new AssertionError('Test bug: QuotaExceededError needs to be tested for using assert_throws_quotaexceedederror()'); + } name = type in codename_name_map ? codename_name_map[type] : type; if (!(name in name_code_map)) { throw new AssertionError('Test bug: unrecognized DOMException code name or name "' + type + '" passed to assert_throws_dom()'); @@ -2307,6 +2446,137 @@ } } + /** + * Assert a `QuotaExceededError` with the expected values is thrown. + * + * There are two ways of calling `assert_throws_quotaexceedederror`: + * + * 1) If the `QuotaExceededError` is expected to come from the + * current global, the first argument should be the function + * expected to throw, the second and a third the expected + * `requested` and `quota` property values, and the fourth, + * optional, argument is the assertion description. + * + * 2) If the `QuotaExceededError` is expected to come from some + * other global, the first argument should be the + * `QuotaExceededError` constructor from that global, the second + * argument should be the function expected to throw, the third + * and fourth the expected `requested` and `quota` property + * values, and the fifth, optional, argument is the assertion + * description. + * + * For the `requested` and `quota` values, instead of `null` or a + * number, the caller can provide a function which determines + * whether the value is acceptable by returning a boolean. + * + */ + function assert_throws_quotaexceedederror(funcOrConstructor, requestedOrFunc, quotaOrRequested, descriptionOrQuota, maybeDescription) + { + let constructor, func, requested, quota, description; + if (funcOrConstructor.name === "QuotaExceededError") { + constructor = funcOrConstructor; + func = requestedOrFunc; + requested = quotaOrRequested; + quota = descriptionOrQuota; + description = maybeDescription; + } else { + constructor = self.QuotaExceededError; + func = funcOrConstructor; + requested = requestedOrFunc; + quota = quotaOrRequested; + description = descriptionOrQuota; + assert(maybeDescription === undefined, + "Too many args passed to no-constructor version of assert_throws_quotaexceedederror"); + } + assert_throws_quotaexceedederror_impl(func, requested, quota, description, "assert_throws_quotaexceedederror", constructor); + } + expose_assert(assert_throws_quotaexceedederror, "assert_throws_quotaexceedederror"); + + /** + * Similar to `assert_throws_quotaexceedederror` but allows + * specifying the assertion type + * (`"assert_throws_quotaexceedederror"` or + * `"promise_rejects_quotaexceedederror"`, in practice). The + * `constructor` argument must be the `QuotaExceededError` + * constructor from the global we expect the exception to come from. + */ + function assert_throws_quotaexceedederror_impl(func, requested, quota, description, assertion_type, constructor) + { + try { + func.call(this); + assert(false, assertion_type, description, "${func} did not throw", + {func}); + } catch (e) { + if (e instanceof AssertionError) { + throw e; + } + + // Basic sanity-checks on the thrown exception. + assert(typeof e === "object", + assertion_type, description, + "${func} threw ${e} with type ${type}, not an object", + {func, e, type:typeof e}); + + assert(e !== null, + assertion_type, description, + "${func} threw null, not an object", + {func}); + + // Sanity-check our requested and quota. + assert(requested === null || + typeof requested === "number" || + typeof requested === "function", + assertion_type, description, + "${requested} is not null, a number, or a function", + {requested}); + assert(quota === null || + typeof quota === "number" || + typeof quota === "function", + assertion_type, description, + "${quota} is not null or a number", + {quota}); + + const required_props = { + code: 22, + name: "QuotaExceededError" + }; + if (typeof requested !== "function") { + required_props.requested = requested; + } + if (typeof quota !== "function") { + required_props.quota = quota; + } + + for (const [prop, expected] of Object.entries(required_props)) { + assert(prop in e && e[prop] == expected, + assertion_type, description, + "${func} threw ${e} that is not a correct QuotaExceededError: property ${prop} is equal to ${actual}, expected ${expected}", + {func, e, prop, actual:e[prop], expected}); + } + + if (typeof requested === "function") { + assert(requested(e.requested), + assertion_type, description, + "${func} threw ${e} that is not a correct QuotaExceededError: requested value ${requested} did not pass the requested predicate", + {func, e, requested}); + } + if (typeof quota === "function") { + assert(quota(e.quota), + assertion_type, description, + "${func} threw ${e} that is not a correct QuotaExceededError: quota value ${quota} did not pass the quota predicate", + {func, e, quota}); + } + + // Check that the exception is from the right global. This check is last + // so more specific, and more informative, checks on the properties can + // happen in case a totally incorrect exception is thrown. + assert(e.constructor === constructor, + assertion_type, description, + "${func} threw an exception from the wrong global", + {func}); + } + } + /** * Assert the provided value is thrown. * @@ -2416,7 +2686,7 @@ function assert_implements(condition, description) { assert(!!condition, "assert_implements", description); } - expose_assert(assert_implements, "assert_implements") + expose_assert(assert_implements, "assert_implements"); /** * Assert that an optional feature is implemented, based on a 'truthy' condition. @@ -2530,11 +2800,11 @@ 2: "Timeout", 3: "Not Run", 4: "Optional Feature Unsupported", - } + }; Test.prototype.format_status = function() { return this.status_formats[this.status]; - } + }; Test.prototype.structured_clone = function() { @@ -2715,7 +2985,8 @@ Test.prototype.step_timeout = function(func, timeout) { var test_this = this; var args = Array.prototype.slice.call(arguments, 2); - return setTimeout(this.step_func(function() { + var local_set_timeout = typeof global_scope.setTimeout === "undefined" ? fake_set_timeout : setTimeout; + return local_set_timeout(this.step_func(function() { return func.apply(test_this, args); }), timeout * tests.timeout_multiplier); }; @@ -2746,6 +3017,7 @@ var timeout_full = timeout * tests.timeout_multiplier; var remaining = Math.ceil(timeout_full / interval); var test_this = this; + var local_set_timeout = typeof global_scope.setTimeout === 'undefined' ? fake_set_timeout : setTimeout; const step = test_this.step_func((result) => { if (result) { @@ -2756,7 +3028,7 @@ "Timed out waiting on condition"); } remaining--; - setTimeout(wait_for_inner, interval); + local_set_timeout(wait_for_inner, interval); } }); @@ -2842,7 +3114,7 @@ return new Promise(resolve => { this.step_wait_func(cond, resolve, description, timeout, interval); }); - } + }; /* * Private method for registering cleanup functions. `testharness.js` @@ -3075,7 +3347,7 @@ throw new Error("AbortController is not supported in this browser"); } return this._abortController.signal; - } + }; /** * A RemoteTest object mirrors a Test object on a remote worker. The @@ -3151,11 +3423,11 @@ function(callback) { callback(); }); - } + }; RemoteTest.prototype.format_status = function() { return Test.prototype.status_formats[this.status]; - } + }; /* * A RemoteContext listens for test events from a remote test context, such @@ -3427,7 +3699,7 @@ this.all_done_callbacks = []; this.hide_test_state = false; - this.pending_remotes = []; + this.remotes = []; this.current_test = null; this.asserts_run = []; @@ -3469,26 +3741,26 @@ for (var p in properties) { if (properties.hasOwnProperty(p)) { var value = properties[p]; - if (p == "allow_uncaught_exception") { + if (p === "allow_uncaught_exception") { this.allow_uncaught_exception = value; - } else if (p == "explicit_done" && value) { + } else if (p === "explicit_done" && value) { this.wait_for_finish = true; - } else if (p == "explicit_timeout" && value) { + } else if (p === "explicit_timeout" && value) { this.timeout_length = null; if (this.timeout_id) { clearTimeout(this.timeout_id); } - } else if (p == "single_test" && value) { + } else if (p === "single_test" && value) { this.set_file_is_test(); - } else if (p == "timeout_multiplier") { + } else if (p === "timeout_multiplier") { this.timeout_multiplier = value; if (this.timeout_length) { this.timeout_length *= this.timeout_multiplier; } - } else if (p == "hide_test_state") { + } else if (p === "hide_test_state") { this.hide_test_state = value; - } else if (p == "output") { + } else if (p === "output") { this.output = value; } else if (p === "debug") { settings.debug = value; @@ -3581,11 +3853,14 @@ Tests.prototype.push = function(test) { + if (this.phase === this.phases.COMPLETE) { + return; + } if (this.phase < this.phases.HAVE_TESTS) { this.start(); } this.num_pending++; - test.index = this.tests.push(test); + test.index = this.tests.push(test) - 1; this.notify_test_state(test); }; @@ -3598,11 +3873,11 @@ }; Tests.prototype.all_done = function() { - return (this.tests.length > 0 || this.pending_remotes.length > 0) && + return (this.tests.length > 0 || this.remotes.length > 0) && test_environment.all_loaded && (this.num_pending === 0 || this.is_aborted) && !this.wait_for_finish && !this.processing_callbacks && - !this.pending_remotes.some(function(w) { return w.running; }); + !this.remotes.some(function(w) { return w.running; }); }; Tests.prototype.start = function() { @@ -3671,7 +3946,8 @@ function(test, testDone) { if (test.phase === test.phases.INITIAL) { - test.phase = test.phases.COMPLETE; + test.phase = test.phases.HAS_RESULT; + test.done(); testDone(); } else { add_test_done_callback(test, testDone); @@ -3682,14 +3958,14 @@ }; Tests.prototype.set_assert = function(assert_name, args) { - this.asserts_run.push(new AssertRecord(this.current_test, assert_name, args)) - } + this.asserts_run.push(new AssertRecord(this.current_test, assert_name, args)); + }; Tests.prototype.set_assert_status = function(index, status, stack) { let assert_record = this.asserts_run[index]; assert_record.status = status; assert_record.stack = stack; - } + }; /** * Update the harness status to reflect an unrecoverable harness error that @@ -3831,7 +4107,7 @@ } var remoteContext = this.create_remote_worker(worker); - this.pending_remotes.push(remoteContext); + this.remotes.push(remoteContext); return remoteContext.done; }; @@ -3854,7 +4130,7 @@ } var remoteContext = this.create_remote_window(remote); - this.pending_remotes.push(remoteContext); + this.remotes.push(remoteContext); return remoteContext.done; }; @@ -4066,8 +4342,8 @@ } else { var root = output_document.documentElement; var is_html = (root && - root.namespaceURI == "http://www.w3.org/1999/xhtml" && - root.localName == "html"); + root.namespaceURI === "http://www.w3.org/1999/xhtml" && + root.localName === "html"); var is_svg = (output_document.defaultView && "SVGSVGElement" in output_document.defaultView && root instanceof output_document.defaultView.SVGSVGElement); @@ -4169,11 +4445,7 @@ status ], ], - ["button", - {"onclick": "let evt = new Event('__test_restart'); " + - "let canceled = !window.dispatchEvent(evt);" + - "if (!canceled) { location.reload() }"}, - "Rerun"] + ["button", {"id":"rerun"}, "Rerun"] ]]; if (harness_status.status === harness_status.ERROR) { @@ -4205,6 +4477,13 @@ log.appendChild(render(summary_template, {num_tests:tests.length}, output_document)); + output_document.getElementById("rerun").addEventListener("click", + function() { + let evt = new Event('__test_restart'); + let canceled = !window.dispatchEvent(evt); + if (!canceled) { location.reload(); } + }); + forEach(output_document.querySelectorAll("section#summary label"), function(element) { @@ -4229,18 +4508,6 @@ }); }); - // This use of innerHTML plus manual escaping is not recommended in - // general, but is necessary here for performance. Using textContent - // on each individual adds tens of seconds of execution time for - // large test suites (tens of thousands of tests). - function escape_html(s) - { - return s.replace(/\&/g, "&") - .replace(/ { - var output_fn = "" + escape_html(assert.assert_name) + "("; - var prefix_len = output_fn.length; - var output_args = assert.args; - var output_len = output_args.reduce((prev, current) => prev+current, prefix_len); - if (output_len[output_len.length - 1] > 50) { - output_args = output_args.map((x, i) => - (i > 0 ? " ".repeat(prefix_len) : "" )+ x + (i < output_args.length - 1 ? ",\n" : "")); - } else { - output_args = output_args.map((x, i) => x + (i < output_args.length - 1 ? ", " : "")); - } - output_fn += escape_html(output_args.join("")); - output_fn += ')'; - var output_location; + + const table = asserts_output.querySelector("table"); + for (const assert of asserts) { + const status_class_name = status_class(Test.prototype.status_formats[assert.status]); + var output_fn = "(" + assert.args.join(", ") + ")"; if (assert.stack) { - output_location = assert.stack.split("\n", 1)[0].replace(/@?\w+:\/\/[^ "\/]+(?::\d+)?/g, " "); + output_fn += "\n"; + output_fn += assert.stack.split("\n", 1)[0].replace(/@?\w+:\/\/[^ "\/]+(?::\d+)?/g, " "); } - return "" + - "" + - Test.prototype.status_formats[assert.status] + "" + - "
" +
-                    output_fn +
-                    (output_location ? "\n" + escape_html(output_location) : "") +
-                    "
"; + table.appendChild(render( + ["tr", {"class":"overall-" + status_class_name}, + ["td", {"class":status_class_name}, Test.prototype.status_formats[assert.status]], + ["td", {}, ["pre", {}, ["strong", {}, assert.assert_name], output_fn]] ])); } - ).join("\n"); - rv += ""; - return rv; + return asserts_output; } - log.appendChild(document.createElementNS(xhtml_ns, "section")); var assertions = has_assertions(); - var html = "

Details

" + - "" + - (assertions ? "" : "") + - "" + - ""; - for (var i = 0; i < tests.length; i++) { - var test = tests[i]; - html += '' + - '"; - } - html += "
ResultTest NameAssertionMessage
' + - test.format_status() + - "" + - escape_html(test.name) + - "" + - (assertions ? escape_html(get_assertion(test)) + "" : "") + - escape_html(test.message ? tests[i].message : " ") + - (tests[i].stack ? "
" +
-                 escape_html(tests[i].stack) +
-                 "
": ""); + const section = render( + ["section", {}, + ["h2", {}, "Details"], + ["table", {"id":"results", "class":(assertions ? "assertions" : "")}, + ["thead", {}, + ["tr", {}, + ["th", {}, "Result"], + ["th", {}, "Test Name"], + (assertions ? ["th", {}, "Assertion"] : ""), + ["th", {}, "Message" ]]], + ["tbody", {}]]]); + + const tbody = section.querySelector("tbody"); + for (const test of tests) { + const status = test.format_status(); + const status_class_name = status_class(status); + tbody.appendChild(render( + ["tr", {"class":"overall-" + status_class_name}, + ["td", {"class":status_class_name}, status], + ["td", {}, test.name], + (assertions ? ["td", {}, get_assertion(test)] : ""), + ["td", {}, + test.message ?? "", + ["pre", {}, test.stack ?? ""]]])); if (!(test instanceof RemoteTest)) { - html += "
Asserts run" + get_asserts_output(test) + "
" + tbody.lastChild.lastChild.appendChild(get_asserts_output(test)); } - html += "
"; - try { - log.lastChild.innerHTML = html; - } catch (e) { - log.appendChild(document.createElementNS(xhtml_ns, "p")) - .textContent = "Setting innerHTML for the log threw an exception."; - log.appendChild(document.createElementNS(xhtml_ns, "pre")) - .textContent = html; } + log.appendChild(section); }; /* @@ -4410,13 +4659,20 @@ { var substitution_re = /\$\{([^ }]*)\}/g; - function do_substitution(input) { + function do_substitution(input) + { var components = input.split(substitution_re); var rv = []; - for (var i = 0; i < components.length; i += 2) { - rv.push(components[i]); - if (components[i + 1]) { - rv.push(String(substitutions[components[i + 1]])); + if (components.length === 1) { + rv = components; + } else if (substitutions) { + for (var i = 0; i < components.length; i += 2) { + if (components[i]) { + rv.push(components[i]); + } + if (substitutions[components[i + 1]]) { + rv.push(String(substitutions[components[i + 1]])); + } } } return rv; @@ -4532,7 +4788,7 @@ */ function AssertionError(message) { - if (typeof message == "string") { + if (typeof message === "string") { message = sanitize_unpaired_surrogates(message); } this.message = message; @@ -4578,7 +4834,7 @@ } return lines.slice(i).join("\n"); - } + }; function OptionalFeatureUnsupportedError(message) { @@ -4767,11 +5023,21 @@ return META_TITLE; } if ('location' in global_scope && 'pathname' in location) { - return location.pathname.substring(location.pathname.lastIndexOf('/') + 1, location.pathname.indexOf('.')); + var filename = location.pathname.substring(location.pathname.lastIndexOf('/') + 1); + return filename.substring(0, filename.indexOf('.')); } return "Untitled"; } + /** Fetches a JSON resource and parses it */ + async function fetch_json(resource) { + const response = await fetch(resource); + return await response.json(); + } + if (!global_scope.GLOBAL || !global_scope.GLOBAL.isShadowRealm()) { + expose(fetch_json, 'fetch_json'); + } + /** * Setup globals */ diff --git a/test/fixtures/wpt/resources/testharnessreport.js b/test/fixtures/wpt/resources/testharnessreport.js index e5cb40fe0ef..405a2d8b06f 100644 --- a/test/fixtures/wpt/resources/testharnessreport.js +++ b/test/fixtures/wpt/resources/testharnessreport.js @@ -14,31 +14,6 @@ * parameters they are called with see testharness.js */ -function dump_test_results(tests, status) { - var results_element = document.createElement("script"); - results_element.type = "text/json"; - results_element.id = "__testharness__results__"; - var test_results = tests.map(function(x) { - return {name:x.name, status:x.status, message:x.message, stack:x.stack} - }); - var data = {test:window.location.href, - tests:test_results, - status: status.status, - message: status.message, - stack: status.stack}; - results_element.textContent = JSON.stringify(data); - - // To avoid a HierarchyRequestError with XML documents, ensure that 'results_element' - // is inserted at a location that results in a valid document. - var parent = document.body - ? document.body // is required in XHTML documents - : document.documentElement; // fallback for optional in HTML5, SVG, etc. - - parent.appendChild(results_element); -} - -add_completion_callback(dump_test_results); - /* If the parent window has a testharness_properties object, * we use this to provide the test settings. This is used by the * default in-browser runner to configure the timeout and the diff --git a/test/fixtures/wpt/resources/web-bluetooth-bidi-test.js b/test/fixtures/wpt/resources/web-bluetooth-bidi-test.js new file mode 100644 index 00000000000..3283fef43fc --- /dev/null +++ b/test/fixtures/wpt/resources/web-bluetooth-bidi-test.js @@ -0,0 +1,408 @@ +'use strict' + +// Convert `manufacturerData` to an array of bluetooth.BluetoothManufacturerData +// defined in +// https://webbluetoothcg.github.io/web-bluetooth/#bluetooth-bidi-definitions. +function convertToBidiManufacturerData(manufacturerData) { + const bidiManufacturerData = []; + for (const key in manufacturerData) { + bidiManufacturerData.push({ + key: parseInt(key), + data: btoa(String.fromCharCode(...manufacturerData[key])) + }) + } + return bidiManufacturerData; +} + +function ArrayToMojoCharacteristicProperties(arr) { + const struct = {}; + arr.forEach(property => { + struct[property] = true; + }); + return struct; +} + +class FakeBluetooth { + constructor() { + this.fake_central_ = null; + } + + // Returns a promise that resolves with a FakeCentral that clients can use + // to simulate events that a device in the Central/Observer role would + // receive as well as monitor the operations performed by the device in the + // Central/Observer role. + // + // A "Central" object would allow its clients to receive advertising events + // and initiate connections to peripherals i.e. operations of two roles + // defined by the Bluetooth Spec: Observer and Central. + // See Bluetooth 4.2 Vol 3 Part C 2.2.2 "Roles when Operating over an + // LE Physical Transport". + async simulateCentral({state}) { + if (this.fake_central_) { + throw 'simulateCentral() should only be called once'; + } + + await test_driver.bidi.bluetooth.simulate_adapter({state: state}); + this.fake_central_ = new FakeCentral(); + return this.fake_central_; + } +} + +// FakeCentral allows clients to simulate events that a device in the +// Central/Observer role would receive as well as monitor the operations +// performed by the device in the Central/Observer role. +class FakeCentral { + constructor() { + this.peripherals_ = new Map(); + } + + // Simulates a peripheral with |address|, |name|, |manufacturerData| and + // |known_service_uuids| that has already been connected to the system. If the + // peripheral existed already it updates its name, manufacturer data, and + // known UUIDs. |known_service_uuids| should be an array of + // BluetoothServiceUUIDs + // https://webbluetoothcg.github.io/web-bluetooth/#typedefdef-bluetoothserviceuuid + // + // Platforms offer methods to retrieve devices that have already been + // connected to the system or weren't connected through the UA e.g. a user + // connected a peripheral through the system's settings. This method is + // intended to simulate peripherals that those methods would return. + async simulatePreconnectedPeripheral( + {address, name, manufacturerData = {}, knownServiceUUIDs = []}) { + await test_driver.bidi.bluetooth.simulate_preconnected_peripheral({ + address: address, + name: name, + manufacturerData: convertToBidiManufacturerData(manufacturerData), + knownServiceUuids: + knownServiceUUIDs.map(uuid => BluetoothUUID.getService(uuid)) + }); + + return this.fetchOrCreatePeripheral_(address); + } + + // Create a fake_peripheral object from the given address. + fetchOrCreatePeripheral_(address) { + let peripheral = this.peripherals_.get(address); + if (peripheral === undefined) { + peripheral = new FakePeripheral(address); + this.peripherals_.set(address, peripheral); + } + return peripheral; + } +} + +class FakePeripheral { + constructor(address) { + this.address = address; + } + + // Adds a fake GATT Service with |uuid| to be discovered when discovering + // the peripheral's GATT Attributes. Returns a FakeRemoteGATTService + // corresponding to this service. |uuid| should be a BluetoothServiceUUIDs + // https://webbluetoothcg.github.io/web-bluetooth/#typedefdef-bluetoothserviceuuid + async addFakeService({uuid}) { + const service_uuid = BluetoothUUID.getService(uuid); + await test_driver.bidi.bluetooth.simulate_service({ + address: this.address, + uuid: service_uuid, + type: 'add', + }); + return new FakeRemoteGATTService(service_uuid, this.address); + } + + // Sets the next GATT Connection request response to |code|. |code| could be + // an HCI Error Code from BT 4.2 Vol 2 Part D 1.3 List Of Error Codes or a + // number outside that range returned by specific platforms e.g. Android + // returns 0x101 to signal a GATT failure + // https://developer.android.com/reference/android/bluetooth/BluetoothGatt.html#GATT_FAILURE + async setNextGATTConnectionResponse({code}) { + const remove_handler = + test_driver.bidi.bluetooth.gatt_connection_attempted.on((event) => { + if (event.address != this.address) { + return; + } + remove_handler(); + test_driver.bidi.bluetooth.simulate_gatt_connection_response({ + address: event.address, + code, + }); + }); + } + + async setNextGATTDiscoveryResponse({code}) { + // No-op for Web Bluetooth Bidi test, it will be removed when migration + // completes. + return Promise.resolve(); + } + + // Simulates a GATT connection response with |code| from the peripheral. + async simulateGATTConnectionResponse(code) { + await test_driver.bidi.bluetooth.simulate_gatt_connection_response( + {address: this.address, code}); + } + + // Simulates a GATT disconnection in the peripheral. + async simulateGATTDisconnection() { + await test_driver.bidi.bluetooth.simulate_gatt_disconnection( + {address: this.address}); + } +} + +class FakeRemoteGATTService { + constructor(service_uuid, peripheral_address) { + this.service_uuid_ = service_uuid; + this.peripheral_address_ = peripheral_address; + } + + // Adds a fake GATT Characteristic with |uuid| and |properties| + // to this fake service. The characteristic will be found when discovering + // the peripheral's GATT Attributes. Returns a FakeRemoteGATTCharacteristic + // corresponding to the added characteristic. + async addFakeCharacteristic({uuid, properties}) { + const characteristic_uuid = BluetoothUUID.getCharacteristic(uuid); + await test_driver.bidi.bluetooth.simulate_characteristic({ + address: this.peripheral_address_, + serviceUuid: this.service_uuid_, + characteristicUuid: characteristic_uuid, + characteristicProperties: ArrayToMojoCharacteristicProperties(properties), + type: 'add' + }); + return new FakeRemoteGATTCharacteristic( + characteristic_uuid, this.service_uuid_, this.peripheral_address_); + } + + // Removes the fake GATT service from its fake peripheral. + async remove() { + await test_driver.bidi.bluetooth.simulate_service({ + address: this.peripheral_address_, + uuid: this.service_uuid_, + type: 'remove' + }); + } +} + +class FakeRemoteGATTCharacteristic { + constructor(characteristic_uuid, service_uuid, peripheral_address) { + this.characteristic_uuid_ = characteristic_uuid; + this.service_uuid_ = service_uuid; + this.peripheral_address_ = peripheral_address; + this.last_written_value_ = {lastValue: null, lastWriteType: 'none'}; + } + + // Adds a fake GATT Descriptor with |uuid| to be discovered when + // discovering the peripheral's GATT Attributes. Returns a + // FakeRemoteGATTDescriptor corresponding to this descriptor. |uuid| should + // be a BluetoothDescriptorUUID + // https://webbluetoothcg.github.io/web-bluetooth/#typedefdef-bluetoothdescriptoruuid + async addFakeDescriptor({uuid}) { + const descriptor_uuid = BluetoothUUID.getDescriptor(uuid); + await test_driver.bidi.bluetooth.simulate_descriptor({ + address: this.peripheral_address_, + serviceUuid: this.service_uuid_, + characteristicUuid: this.characteristic_uuid_, + descriptorUuid: descriptor_uuid, + type: 'add' + }); + return new FakeRemoteGATTDescriptor( + descriptor_uuid, this.characteristic_uuid_, this.service_uuid_, + this.peripheral_address_); + } + + // Simulate a characteristic for operation |type| with response |code| and + // |data|. + async simulateResponse(type, code, data) { + await test_driver.bidi.bluetooth.simulate_characteristic_response({ + address: this.peripheral_address_, + serviceUuid: this.service_uuid_, + characteristicUuid: this.characteristic_uuid_, + type, + code, + data, + }); + } + + // Simulate a characteristic response for read operation with response |code| + // and |data|. + async simulateReadResponse(code, data) { + await this.simulateResponse('read', code, data); + } + + // Simulate a characteristic response for write operation with response + // |code|. + async simulateWriteResponse(code) { + await this.simulateResponse('write', code); + } + + // Sets the next read response for characteristic to |code| and |value|. + // |code| could be a GATT Error Response from + // BT 4.2 Vol 3 Part F 3.4.1.1 Error Response or a number outside that range + // returned by specific platforms e.g. Android returns 0x101 to signal a GATT + // failure. + // https://developer.android.com/reference/android/bluetooth/BluetoothGatt.html#GATT_FAILURE + async setNextReadResponse(gatt_code, value = null) { + if (gatt_code === 0 && value === null) { + throw '|value| can\'t be null if read should success.'; + } + if (gatt_code !== 0 && value !== null) { + throw '|value| must be null if read should fail.'; + } + + const remove_handler = + test_driver.bidi.bluetooth.characteristic_event_generated.on( + (event) => { + if (event.address != this.peripheral_address_) { + return; + } + remove_handler(); + this.simulateReadResponse(gatt_code, value); + }); + } + + // Sets the next write response for this characteristic to |code|. If + // writing to a characteristic that only supports 'write-without-response' + // the set response will be ignored. + // |code| could be a GATT Error Response from + // BT 4.2 Vol 3 Part F 3.4.1.1 Error Response or a number outside that range + // returned by specific platforms e.g. Android returns 0x101 to signal a GATT + // failure. + async setNextWriteResponse(gatt_code) { + const remove_handler = + test_driver.bidi.bluetooth.characteristic_event_generated.on( + (event) => { + if (event.address != this.peripheral_address_) { + return; + } + this.last_written_value_ = { + lastValue: event.data, + lastWriteType: event.type + }; + remove_handler(); + if (event.type == 'write-with-response') { + this.simulateWriteResponse(gatt_code); + } + }); + } + + // Gets the last successfully written value to the characteristic and its + // write type. Write type is one of 'none', 'default-deprecated', + // 'with-response', 'without-response'. Returns {lastValue: null, + // lastWriteType: 'none'} if no value has yet been written to the + // characteristic. + async getLastWrittenValue() { + return this.last_written_value_; + } + + // Removes the fake GATT Characteristic from its fake service. + async remove() { + await test_driver.bidi.bluetooth.simulate_characteristic({ + address: this.peripheral_address_, + serviceUuid: this.service_uuid_, + characteristicUuid: this.characteristic_uuid_, + characteristicProperties: undefined, + type: 'remove' + }); + } +} + +class FakeRemoteGATTDescriptor { + constructor( + descriptor_uuid, characteristic_uuid, service_uuid, peripheral_address) { + this.descriptor_uuid_ = descriptor_uuid; + this.characteristic_uuid_ = characteristic_uuid; + this.service_uuid_ = service_uuid; + this.peripheral_address_ = peripheral_address; + this.last_written_value_ = null; + } + + // Simulate a descriptor for operation |type| with response |code| and + // |data|. + async simulateResponse(type, code, data) { + await test_driver.bidi.bluetooth.simulate_descriptor_response({ + address: this.peripheral_address_, + serviceUuid: this.service_uuid_, + characteristicUuid: this.characteristic_uuid_, + descriptorUuid: this.descriptor_uuid_, + type, + code, + data, + }); + } + + // Simulate a descriptor response for read operation with response |code| and + // |data|. + async simulateReadResponse(code, data) { + await this.simulateResponse('read', code, data); + } + + // Simulate a descriptor response for write operation with response |code|. + async simulateWriteResponse(code) { + await this.simulateResponse('write', code); + } + + // Sets the next read response for descriptor to |code| and |value|. + // |code| could be a GATT Error Response from + // BT 4.2 Vol 3 Part F 3.4.1.1 Error Response or a number outside that range + // returned by specific platforms e.g. Android returns 0x101 to signal a GATT + // failure. + // https://developer.android.com/reference/android/bluetooth/BluetoothGatt.html#GATT_FAILURE + async setNextReadResponse(gatt_code, value = null) { + if (gatt_code === 0 && value === null) { + throw '|value| can\'t be null if read should success.'; + } + if (gatt_code !== 0 && value !== null) { + throw '|value| must be null if read should fail.'; + } + + const remove_handler = + test_driver.bidi.bluetooth.descriptor_event_generated.on((event) => { + if (event.address != this.peripheral_address_) { + return; + } + remove_handler(); + this.simulateReadResponse(gatt_code, value); + }); + } + + // Sets the next write response for this descriptor to |code|. + // |code| could be a GATT Error Response from + // BT 4.2 Vol 3 Part F 3.4.1.1 Error Response or a number outside that range + // returned by specific platforms e.g. Android returns 0x101 to signal a GATT + // failure. + async setNextWriteResponse(gatt_code) { + const remove_handler = + test_driver.bidi.bluetooth.descriptor_event_generated.on((event) => { + if (event.address != this.peripheral_address_) { + return; + } + this.last_written_value_ = { + lastValue: event.data, + lastWriteType: event.type + }; + remove_handler(); + if (event.type == 'write-with-response') { + this.simulateWriteResponse(gatt_code); + } + }); + } + + // Gets the last successfully written value to the descriptor. + // Returns null if no value has yet been written to the descriptor. + async getLastWrittenValue() { + return this.last_written_value_; + } + + // Removes the fake GATT Descriptor from its fake characteristic. + async remove() { + await test_driver.bidi.bluetooth.simulate_descriptor({ + address: this.peripheral_address_, + serviceUuid: this.service_uuid_, + characteristicUuid: this.characteristic_uuid_, + descriptorUuid: this.descriptor_uuid_, + type: 'remove' + }); + } +} + +function initializeBluetoothBidiResources() { + navigator.bluetooth.test = new FakeBluetooth(); +} diff --git a/test/fixtures/wpt/versions.json b/test/fixtures/wpt/versions.json index 86b594dd5ef..5c4c0305254 100644 --- a/test/fixtures/wpt/versions.json +++ b/test/fixtures/wpt/versions.json @@ -64,7 +64,7 @@ "path": "resource-timing" }, "resources": { - "commit": "1e140d63ec885703ce24b3798abd81912696bb85", + "commit": "1d2c5fb36a6e477c8f915bde7eca027be6abe792", "path": "resources" }, "streams": { @@ -96,7 +96,7 @@ "path": "web-locks" }, "WebCryptoAPI": { - "commit": "ab08796857072c5a93e27e0a25effba2e07dad11", + "commit": "1d2c5fb36a6e477c8f915bde7eca027be6abe792", "path": "WebCryptoAPI" }, "webidl/ecmascript-binding/es-exceptions": { @@ -108,7 +108,7 @@ "path": "webmessaging/broadcastchannel" }, "webstorage": { - "commit": "1291340aaaa6e73db43b412e47401eca3830c556", + "commit": "1d2c5fb36a6e477c8f915bde7eca027be6abe792", "path": "webstorage" } } diff --git a/test/fixtures/wpt/webstorage/storage_local_setitem_quotaexceedederr.window.js b/test/fixtures/wpt/webstorage/storage_local_setitem_quotaexceedederr.window.js index fff7d6444a0..f2f3c4d6887 100644 --- a/test/fixtures/wpt/webstorage/storage_local_setitem_quotaexceedederr.window.js +++ b/test/fixtures/wpt/webstorage/storage_local_setitem_quotaexceedederr.window.js @@ -1,16 +1,18 @@ -test(function() { +test(t => { localStorage.clear(); var index = 0; var key = "name"; var val = "x".repeat(1024); - assert_throws_dom("QUOTA_EXCEEDED_ERR", function() { + t.add_cleanup(() => { + localStorage.clear(); + }); + + assert_throws_quotaexceedederror(() => { while (true) { index++; localStorage.setItem("" + key + index, "" + val + index); } - }); - - localStorage.clear(); + }, null, null); }, "Throws QuotaExceededError when the quota has been exceeded"); diff --git a/test/fixtures/wpt/webstorage/storage_session_setitem_quotaexceedederr.window.js b/test/fixtures/wpt/webstorage/storage_session_setitem_quotaexceedederr.window.js index 42a895470ef..693c98d29f9 100644 --- a/test/fixtures/wpt/webstorage/storage_session_setitem_quotaexceedederr.window.js +++ b/test/fixtures/wpt/webstorage/storage_session_setitem_quotaexceedederr.window.js @@ -1,16 +1,18 @@ -test(function() { +test(t => { sessionStorage.clear(); var index = 0; var key = "name"; var val = "x".repeat(1024); - assert_throws_dom("QUOTA_EXCEEDED_ERR", function() { + t.add_cleanup(() => { + sessionStorage.clear(); + }); + + assert_throws_quotaexceedederror(() => { while (true) { index++; sessionStorage.setItem("" + key + index, "" + val + index); } - }); - - sessionStorage.clear(); + }, null, null); }, "Throws QuotaExceededError when the quota has been exceeded"); diff --git a/test/fixtures/wpt/webstorage/symbol-props.window.js b/test/fixtures/wpt/webstorage/symbol-props.window.js index 61dd8f83dc4..8f598d70769 100644 --- a/test/fixtures/wpt/webstorage/symbol-props.window.js +++ b/test/fixtures/wpt/webstorage/symbol-props.window.js @@ -39,10 +39,10 @@ Object.defineProperty(storage, key, { "value": "test", "configurable": false }); assert_equals(storage[key], "test"); var desc = Object.getOwnPropertyDescriptor(storage, key); - assert_true(desc.configurable, "configurable"); + assert_false(desc.configurable, "configurable"); - assert_true(delete storage[key]); - assert_equals(storage[key], undefined); + assert_false(delete storage[key]); + assert_equals(storage[key], "test"); }, name + ": defineProperty not configurable"); test(function() { diff --git a/test/wpt/status/WebCryptoAPI.cjs b/test/wpt/status/WebCryptoAPI.cjs index 709d34b8f47..7e4639ca497 100644 --- a/test/wpt/status/WebCryptoAPI.cjs +++ b/test/wpt/status/WebCryptoAPI.cjs @@ -26,4 +26,20 @@ module.exports = { ], }, }, + 'getRandomValues.any.js': { + 'fail': { + 'note': 'https://github.com/nodejs/node/issues/58987', + 'expected': [ + 'Large length: Int8Array', + 'Large length: Int16Array', + 'Large length: Int32Array', + 'Large length: BigInt64Array', + 'Large length: Uint8Array', + 'Large length: Uint8ClampedArray', + 'Large length: Uint16Array', + 'Large length: Uint32Array', + 'Large length: BigUint64Array', + ], + }, + }, }; diff --git a/test/wpt/status/webstorage.json b/test/wpt/status/webstorage.json index 10171601480..1dad4e2dd48 100644 --- a/test/wpt/status/webstorage.json +++ b/test/wpt/status/webstorage.json @@ -22,5 +22,30 @@ }, "storage_session_window_reopen.window.js": { "skip": "window.open() is not supported in Node.js." + }, + "storage_session_setitem_quotaexceedederr.window.js": { + "fail": { + "note": "https://github.com/nodejs/node/issues/58987", + "expected": [ + "Throws QuotaExceededError when the quota has been exceeded" + ] + } + }, + "storage_local_setitem_quotaexceedederr.window.js": { + "fail": { + "note": "https://github.com/nodejs/node/issues/58987", + "expected": [ + "Throws QuotaExceededError when the quota has been exceeded" + ] + } + }, + "symbol-props.window.js": { + "fail": { + "note": "https://github.com/nodejs/node/issues/59310", + "expected": [ + "localStorage: defineProperty not configurable", + "sessionStorage: defineProperty not configurable" + ] + } } }