diff --git a/src/vs/editor/browser/controller/editContext/native/nativeEditContext.ts b/src/vs/editor/browser/controller/editContext/native/nativeEditContext.ts index 3836bdf8e1d..2d502ae65b1 100644 --- a/src/vs/editor/browser/controller/editContext/native/nativeEditContext.ts +++ b/src/vs/editor/browser/controller/editContext/native/nativeEditContext.ts @@ -90,6 +90,7 @@ export class NativeEditContext extends AbstractEditContext { this._imeTextArea.setClassName(`ime-text-area`); this._imeTextArea.setAttribute('readonly', 'true'); this._imeTextArea.setAttribute('tabindex', '-1'); + this._imeTextArea.setAttribute('aria-hidden', 'true'); this.domNode.setAttribute('autocorrect', 'off'); this.domNode.setAttribute('autocapitalize', 'off'); this.domNode.setAttribute('autocomplete', 'off'); diff --git a/test/monaco/monaco.test.ts b/test/monaco/monaco.test.ts index 078fb2cb69e..d921664a747 100644 --- a/test/monaco/monaco.test.ts +++ b/test/monaco/monaco.test.ts @@ -5,6 +5,7 @@ import * as playwright from '@playwright/test'; import { assert } from 'chai'; +import { injectAxe } from 'axe-playwright'; const PORT = 8563; const TIMEOUT = 20 * 1000; @@ -135,4 +136,127 @@ describe('API Integration Tests', function (): void { '\t\t\'\'\'Make the monkey eat N bananas!\'\'\'' ]); }); + describe('Accessibility', function (): void { + beforeEach(async () => { + await page.goto(APP); + await injectAxe(page); + await page.evaluate(` + (function () { + instance.focus(); + instance.trigger('keyboard', 'cursorHome'); + instance.trigger('keyboard', 'type', { + text: 'a' + }); + })() + `); + }); + + it('Editor should not have critical accessibility violations', async () => { + let violationCount = 0; + const checkedElements = new Set(); + + // Run axe and get all results (passes and violations) + const axeResults = await page.evaluate(() => { + return window.axe.run(document, { + runOnly: { + type: 'tag', + values: [ + 'wcag2a', + 'wcag2aa', + 'wcag21a', + 'wcag21aa', + 'best-practice' + ] + } + }); + }); + + axeResults.violations.forEach((v: any) => { + const isCritical = v.impact === 'critical'; + const emoji = isCritical ? '❌' : undefined; + v.nodes.forEach((node: any) => { + const selector = node.target?.join(' '); + if (selector && emoji) { + checkedElements.add(selector); + console.log(`${emoji} FAIL: ${selector} - ${v.id} - ${v.description}`); + } + }); + violationCount += isCritical ? 1 : 0; + }); + + axeResults.passes.forEach((pass: any) => { + pass.nodes.forEach((node: any) => { + const selector = node.target?.join(' '); + if (selector && !checkedElements.has(selector)) { + checkedElements.add(selector); + } + }); + }); + + playwright.expect(violationCount).toBe(0); + }); + + it('Editor should not have color contrast accessibility violations', async () => { + let violationCount = 0; + const checkedElements = new Set(); + + const axeResults = await page.evaluate(() => { + return window.axe.run(document, { + runOnly: { + type: 'rule', + values: ['color-contrast'] + } + }); + }); + + axeResults.violations.forEach((v: any) => { + const isCritical = v.impact === 'critical'; + const emoji = isCritical ? '❌' : undefined; + v.nodes.forEach((node: any) => { + const selector = node.target?.join(' '); + if (selector && emoji) { + checkedElements.add(selector); + console.log(`${emoji} FAIL: ${selector} - ${v.id} - ${v.description}`); + } + }); + violationCount += 1; + }); + + axeResults.passes.forEach((pass: any) => { + pass.nodes.forEach((node: any) => { + const selector = node.target?.join(' '); + if (selector && !checkedElements.has(selector)) { + checkedElements.add(selector); + } + }); + }); + + playwright.expect(violationCount).toBe(0); + }); + it('Monaco editor container should have an ARIA role', async () => { + const role = await page.evaluate(() => { + const container = document.querySelector('.monaco-editor'); + return container?.getAttribute('role'); + }); + assert.isDefined(role, 'Monaco editor container should have a role attribute'); + }); + + it('Monaco editor should have an ARIA label', async () => { + const ariaLabel = await page.evaluate(() => { + const container = document.querySelector('.monaco-editor'); + return container?.getAttribute('aria-label'); + }); + assert.isDefined(ariaLabel, 'Monaco editor container should have an aria-label attribute'); + }); + + it('All toolbar buttons should have accessible names', async () => { + const buttonsWithoutLabel = await page.evaluate(() => { + return Array.from(document.querySelectorAll('button')).filter(btn => { + const label = btn.getAttribute('aria-label') || btn.textContent?.trim(); + return !label; + }).map(btn => btn.outerHTML); + }); + assert.deepEqual(buttonsWithoutLabel, [], 'All toolbar buttons should have accessible names'); + }); + }); }); diff --git a/test/monaco/package-lock.json b/test/monaco/package-lock.json index 0b5274978bb..ada3052b87e 100644 --- a/test/monaco/package-lock.json +++ b/test/monaco/package-lock.json @@ -10,6 +10,7 @@ "license": "MIT", "devDependencies": { "@types/chai": "^4.2.14", + "axe-playwright": "^2.1.0", "chai": "^4.2.0", "warnings-to-errors-webpack-plugin": "^2.3.0" } @@ -20,6 +21,13 @@ "integrity": "sha512-G+ITQPXkwTrslfG5L/BksmbLUA0M1iybEsmCWPqzSxsRRhJZimBKJkoMi8fr/CPygPTj4zO5pJH7I2/cm9M7SQ==", "dev": true }, + "node_modules/@types/junit-report-builder": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/junit-report-builder/-/junit-report-builder-3.0.2.tgz", + "integrity": "sha512-R5M+SYhMbwBeQcNXYWNCZkl09vkVfAtcPIaCGdzIkkbeaTrVbGQ7HVgi4s+EmM/M1K4ZuWQH0jGcvMvNePfxYA==", + "dev": true, + "license": "MIT" + }, "node_modules/assertion-error": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", @@ -29,6 +37,49 @@ "node": "*" } }, + "node_modules/axe-core": { + "version": "4.10.3", + "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.10.3.tgz", + "integrity": "sha512-Xm7bpRXnDSX2YE2YFfBk2FnF0ep6tmG7xPh8iHee8MIcrgq762Nkce856dYtJYLkuIoYZvGfTs/PbZhideTcEg==", + "dev": true, + "license": "MPL-2.0", + "engines": { + "node": ">=4" + } + }, + "node_modules/axe-html-reporter": { + "version": "2.2.11", + "resolved": "https://registry.npmjs.org/axe-html-reporter/-/axe-html-reporter-2.2.11.tgz", + "integrity": "sha512-WlF+xlNVgNVWiM6IdVrsh+N0Cw7qupe5HT9N6Uyi+aN7f6SSi92RDomiP1noW8OWIV85V6x404m5oKMeqRV3tQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "mustache": "^4.0.1" + }, + "engines": { + "node": ">=8.9.0" + }, + "peerDependencies": { + "axe-core": ">=3" + } + }, + "node_modules/axe-playwright": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/axe-playwright/-/axe-playwright-2.1.0.tgz", + "integrity": "sha512-tY48SX56XaAp16oHPyD4DXpybz8Jxdz9P7exTjF/4AV70EGUavk+1fUPWirM0OYBR+YyDx6hUeDvuHVA6fB9YA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/junit-report-builder": "^3.0.2", + "axe-core": "^4.10.1", + "axe-html-reporter": "2.2.11", + "junit-report-builder": "^5.1.1", + "picocolors": "^1.1.1" + }, + "peerDependencies": { + "playwright": ">1.0.0" + } + }, "node_modules/chai": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/chai/-/chai-4.2.0.tgz", @@ -77,6 +128,54 @@ "node": "*" } }, + "node_modules/junit-report-builder": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/junit-report-builder/-/junit-report-builder-5.1.1.tgz", + "integrity": "sha512-ZNOIIGMzqCGcHQEA2Q4rIQQ3Df6gSIfne+X9Rly9Bc2y55KxAZu8iGv+n2pP0bLf0XAOctJZgeloC54hWzCahQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "lodash": "^4.17.21", + "make-dir": "^3.1.0", + "xmlbuilder": "^15.1.1" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "dev": true, + "license": "MIT" + }, + "node_modules/make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^6.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mustache": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/mustache/-/mustache-4.2.0.tgz", + "integrity": "sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ==", + "dev": true, + "license": "MIT", + "bin": { + "mustache": "bin/mustache" + } + }, "node_modules/pathval": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", @@ -86,6 +185,23 @@ "node": "*" } }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, "node_modules/type-detect": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", @@ -103,6 +219,16 @@ "peerDependencies": { "webpack": "^2.2.0-rc || ^3 || ^4 || ^5" } + }, + "node_modules/xmlbuilder": { + "version": "15.1.1", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-15.1.1.tgz", + "integrity": "sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.0" + } } } } diff --git a/test/monaco/package.json b/test/monaco/package.json index 2d48ca1a73f..c7373919431 100644 --- a/test/monaco/package.json +++ b/test/monaco/package.json @@ -12,6 +12,7 @@ }, "devDependencies": { "@types/chai": "^4.2.14", + "axe-playwright": "^2.1.0", "chai": "^4.2.0", "warnings-to-errors-webpack-plugin": "^2.3.0" }