add editor a11y playwright tests (#255899)

This commit is contained in:
Megan Rogge
2025-07-14 23:38:21 -04:00
committed by GitHub
parent dd0856ddda
commit dff97cf6a9
4 changed files with 252 additions and 0 deletions

View File

@@ -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');

View File

@@ -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<string>();
// 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<string>();
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');
});
});
});

View File

@@ -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"
}
}
}
}

View File

@@ -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"
}