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>/, ` - + +