diff --git a/arianotify-polyfill.js b/arianotify-polyfill.js
index aeb7fd7..297bcbf 100644
--- a/arianotify-polyfill.js
+++ b/arianotify-polyfill.js
@@ -3,10 +3,16 @@
const domAPIsAreAvailable =
typeof globalThis.Element !== "undefined" &&
typeof globalThis.Document !== "undefined";
+const shouldBypassNativeAriaNotify =
+ /** @type {typeof globalThis & {__bypassNativeAriaNotify?: boolean}} */ (
+ globalThis
+ ).__bypassNativeAriaNotify === true;
if (
domAPIsAreAvailable &&
- (!("ariaNotify" in Element.prototype) || !("ariaNotify" in Document.prototype))
+ (shouldBypassNativeAriaNotify ||
+ !("ariaNotify" in Element.prototype) ||
+ !("ariaNotify" in Document.prototype))
) {
/** @type {string} */
let uniqueId = `${Date.now()}`;
@@ -200,31 +206,43 @@ if (
AssertiveLiveRegionCustomElement
);
- if (!("ariaNotify" in Element.prototype)) {
- /**
- * @param {string} message
- * @param {object} options
- * @param {"high" | "normal"} [options.priority]
- */
- Element.prototype["ariaNotify"] = function (
- message,
- { priority = "normal" } = {}
- ) {
- queue.enqueue(new Message({ element: this, message, priority }));
- };
+ /**
+ * @param {string} message
+ * @param {object} options
+ * @param {"high" | "normal"} [options.priority]
+ */
+ const elementAriaNotify = function (
+ message,
+ { priority = "normal" } = {}
+ ) {
+ queue.enqueue(new Message({ element: this, message, priority }));
+ };
+
+ if (shouldBypassNativeAriaNotify || !("ariaNotify" in Element.prototype)) {
+ Object.defineProperty(Element.prototype, "ariaNotify", {
+ configurable: true,
+ writable: true,
+ value: elementAriaNotify,
+ });
}
- if (!("ariaNotify" in Document.prototype)) {
- /**
- * @param {string} message
- * @param {object} options
- * @param {"high" | "normal"} [options.priority]
- */
- Document.prototype["ariaNotify"] = function (
- message,
- { priority = "normal" } = {}
- ) {
- queue.enqueue(new Message({ element: this.documentElement, message, priority }));
- };
+ /**
+ * @param {string} message
+ * @param {object} options
+ * @param {"high" | "normal"} [options.priority]
+ */
+ const documentAriaNotify = function (
+ message,
+ { priority = "normal" } = {}
+ ) {
+ queue.enqueue(new Message({ element: this.documentElement, message, priority }));
+ };
+
+ if (shouldBypassNativeAriaNotify || !("ariaNotify" in Document.prototype)) {
+ Object.defineProperty(Document.prototype, "ariaNotify", {
+ configurable: true,
+ writable: true,
+ value: documentAriaNotify,
+ });
}
}
diff --git a/package-lock.json b/package-lock.json
index ee09bc6..47d046e 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -13,8 +13,7 @@
"@guidepup/guidepup": "^0.24.0",
"@guidepup/playwright": "^0.15.0",
"@playwright/test": "^1.48.0",
- "@web/test-runner": "^0.20.1",
- "@web/test-runner-playwright": "^0.11.1"
+ "@web/test-runner": "^0.20.1"
}
},
"node_modules/@babel/code-frame": {
@@ -1083,21 +1082,6 @@
"node": ">=18.0.0"
}
},
- "node_modules/@web/test-runner-playwright": {
- "version": "0.11.1",
- "resolved": "https://registry.npmjs.org/@web/test-runner-playwright/-/test-runner-playwright-0.11.1.tgz",
- "integrity": "sha512-l9tmX0LtBqMaKAApS4WshpB87A/M8sOHZyfCobSGuYqnREgz5rqQpX314yx+4fwHXLLTa5N64mTrawsYkLjliw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@web/test-runner-core": "^0.13.0",
- "@web/test-runner-coverage-v8": "^0.8.0",
- "playwright": "^1.53.0"
- },
- "engines": {
- "node": ">=18.0.0"
- }
- },
"node_modules/accepts": {
"version": "1.3.8",
"resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
diff --git a/package.json b/package.json
index 871a2e7..b727f52 100644
--- a/package.json
+++ b/package.json
@@ -17,15 +17,14 @@
},
"files": [],
"scripts": {
- "test": "npx playwright install firefox && web-test-runner",
- "test:guidepup": "npx playwright install firefox && npx playwright test"
+ "test": "web-test-runner",
+ "test:guidepup": "npx playwright test"
},
"devDependencies": {
"@esm-bundle/chai": "^4.3.4-fix.0",
"@guidepup/guidepup": "^0.24.0",
"@guidepup/playwright": "^0.15.0",
"@playwright/test": "^1.48.0",
- "@web/test-runner": "^0.20.1",
- "@web/test-runner-playwright": "^0.11.1"
+ "@web/test-runner": "^0.20.1"
}
}
diff --git a/playwright.config.mjs b/playwright.config.mjs
index 506e134..70556a0 100644
--- a/playwright.config.mjs
+++ b/playwright.config.mjs
@@ -9,8 +9,11 @@ const config = {
retries: 0,
projects: [
{
- name: 'firefox', // Use Firefox because Firefox doesn’t have a native implementation of 'ariaNotify' (as of 2026-01-15), so we can test the polyfill in it.
- use: devices['Desktop Firefox'],
+ name: "Google Chrome",
+ use: {
+ ...devices["Desktop Chrome"],
+ channel: "chrome",
+ },
},
],
quiet: false,
diff --git a/tests/bypass-native-arianotify.js b/tests/bypass-native-arianotify.js
new file mode 100644
index 0000000..f707754
--- /dev/null
+++ b/tests/bypass-native-arianotify.js
@@ -0,0 +1,15 @@
+// @ts-check
+
+for (const prototype of [Element.prototype, Document.prototype]) {
+ Object.defineProperty(prototype, "ariaNotify", {
+ configurable: true,
+ writable: true,
+ value() {
+ throw new Error("Expected tests to use the ariaNotify polyfill");
+ },
+ });
+}
+
+/** @type {typeof globalThis & {__bypassNativeAriaNotify?: boolean}} */ (
+ globalThis
+).__bypassNativeAriaNotify = true;
diff --git a/tests/guidepup/nvda.spec.mjs b/tests/guidepup/nvda.spec.mjs
index f5b7954..9d3ed7f 100644
--- a/tests/guidepup/nvda.spec.mjs
+++ b/tests/guidepup/nvda.spec.mjs
@@ -25,6 +25,13 @@ import path from "node:path";
const test = baseTest.extend({
context: async ({ context }, run) => {
+ await context.addInitScript({
+ path: path.join(
+ import.meta.dirname,
+ "..",
+ "bypass-native-arianotify.js"
+ ),
+ });
await context.route("**/*", (route, request) =>
route.fulfill({
path: path.join(
diff --git a/tests/guidepup/voiceover.spec.mjs b/tests/guidepup/voiceover.spec.mjs
index 42bcaaf..dac4f8c 100644
--- a/tests/guidepup/voiceover.spec.mjs
+++ b/tests/guidepup/voiceover.spec.mjs
@@ -9,6 +9,13 @@ import path from "node:path";
const test = baseTest.extend({
context: async ({ context }, run) => {
+ await context.addInitScript({
+ path: path.join(
+ import.meta.dirname,
+ "..",
+ "bypass-native-arianotify.js"
+ ),
+ });
await context.route("**/*", (route, request) =>
route.fulfill({
path: path.join(
diff --git a/tests/web-test-runner/arianotify-polyfill.js b/tests/web-test-runner/arianotify-polyfill.js
deleted file mode 120000
index fcfb9f4..0000000
--- a/tests/web-test-runner/arianotify-polyfill.js
+++ /dev/null
@@ -1 +0,0 @@
-../../arianotify-polyfill.js
\ No newline at end of file
diff --git a/web-test-runner.config.js b/web-test-runner.config.js
index 4819f27..d2afcfe 100644
--- a/web-test-runner.config.js
+++ b/web-test-runner.config.js
@@ -1,15 +1,7 @@
-import { playwrightLauncher } from '@web/test-runner-playwright';
-
export default {
files: "tests/web-test-runner/*.test.html",
coverage: true,
nodeResolve: true,
- browsers: [
- playwrightLauncher({
- product: 'firefox', // Use Firefox instead of Chrome (web-test-runner’s default), because Firefox doesn’t have a native implementation of 'ariaNotify' (as of 2025-11-07), so we can test the polyfill in it.
- launchOptions: { headless: true }
- }),
- ],
plugins: [
{
name: "include-polyfill",
@@ -18,7 +10,8 @@ export default {
return context.body.replace(
/<\/body>/,
`
-
+
+