diff --git a/src/locales/en.json b/src/locales/en.json
index e33cdef8bbb..a8602ad35e0 100644
--- a/src/locales/en.json
+++ b/src/locales/en.json
@@ -141,6 +141,12 @@
"pad.importExport.exportword": "Microsoft Word",
"pad.importExport.exportpdf": "PDF",
"pad.importExport.exportopen": "ODF (Open Document Format)",
+ "pad.importExport.exportetherpada.title": "Export as Etherpad",
+ "pad.importExport.exporthtmla.title": "Export as HTML",
+ "pad.importExport.exportplaina.title": "Export as plain text",
+ "pad.importExport.exportworda.title": "Export as Microsoft Word",
+ "pad.importExport.exportpdfa.title": "Export as PDF",
+ "pad.importExport.exportopena.title": "Export as ODF (Open Document Format)",
"pad.importExport.noConverter.innerHTML": "You can only import from plain text or HTML formats. For more advanced import features, please install LibreOffice.",
"pad.modals.connected": "Connected.",
diff --git a/src/templates/pad.html b/src/templates/pad.html
index 5212b004606..8a4e3ac3c80 100644
--- a/src/templates/pad.html
+++ b/src/templates/pad.html
@@ -323,23 +323,23 @@
diff --git a/src/tests/frontend-new/specs/a11y_dialogs.spec.ts b/src/tests/frontend-new/specs/a11y_dialogs.spec.ts
index c5891ae1f06..ec6406a3205 100644
--- a/src/tests/frontend-new/specs/a11y_dialogs.spec.ts
+++ b/src/tests/frontend-new/specs/a11y_dialogs.spec.ts
@@ -1,6 +1,12 @@
import {expect, test} from '@playwright/test';
import {goToNewPad} from '../helper/padHelper';
+// Pin browser locale so html10n picks the English bundle. Several
+// assertions in this file compare against specific English strings
+// (e.g. "Close chat", "Export as Etherpad"); without this, translatewiki
+// updates would localise those strings and break the suite.
+test.use({locale: 'en-US'});
+
test.beforeEach(async ({page}) => {
await goToNewPad(page);
});
@@ -65,28 +71,31 @@ test('users popup closes on Escape even when focus is outside the popup', async
await expect(dialog).not.toHaveClass(/popup-show/);
});
-test('export links have an accessible name from their localized content', async ({page}) => {
+test('export links have a localized aria-label and matching title', async ({page}) => {
await page.locator('button[data-l10n-id="pad.toolbar.import_export.title"]').click();
// The Word/PDF/ODF export links are removed client-side by pad_impexp.ts
// when soffice is not configured, so only assert on links that the
- // environment actually renders. For the ones that are present, their
- // accessible name comes from the localized child span (data-l10n-id
- // pad.importExport.exportetherpad etc.), not a hard-coded English
- // aria-label. Assert the visible text is non-empty, which is what a
- // screen reader will announce.
- const ids = [
- '#exportetherpada',
- '#exporthtmla',
- '#exportplaina',
- '#exportworda',
- '#exportpdfa',
- '#exportopena',
+ // environment actually renders. Each anchor carries
+ // data-l10n-id="pad.importExport.exporta.title", which html10n
+ // expands into both `title` and `aria-label` from the same translation
+ // (e.g. "Export as Etherpad"). The inner icon span is aria-hidden so a
+ // screen reader announces the anchor's label once, not twice.
+ const cases: Array<[string, string]> = [
+ ['#exportetherpada', 'Export as Etherpad'],
+ ['#exporthtmla', 'Export as HTML'],
+ ['#exportplaina', 'Export as plain text'],
+ ['#exportworda', 'Export as Microsoft Word'],
+ ['#exportpdfa', 'Export as PDF'],
+ ['#exportopena', 'Export as ODF (Open Document Format)'],
];
- for (const id of ids) {
+ for (const [id, expected] of cases) {
const locator = page.locator(id);
if ((await locator.count()) === 0) continue;
- const text = (await locator.innerText()).trim();
- expect(text.length).toBeGreaterThan(0);
+ await expect(locator).toHaveAttribute('aria-label', expected);
+ await expect(locator).toHaveAttribute('title', expected);
+ await expect(locator).toHaveAttribute('rel', 'noopener');
+ const innerSpan = locator.locator('span.exporttype');
+ await expect(innerSpan).toHaveAttribute('aria-hidden', 'true');
}
});