Prevent screen reader from announcing checkbox label twice
Due to duplicate labeling from the aria-label of the checkbox and from the label element's for attribute, screen readers announce the checkbox label twice. This patch prevents that by removing the aria-label on the checkbox and instead setting a title attribute.
Note the for attribute on the checkbox label was last added in this CL - https://chromium-review.googlesource.com/c/devtools/devtools-frontend/+/2077666
to address a regression introduced here - https://chromium-review.googlesource.com/c/devtools/devtools-frontend/+/2062603.
There's a bit of friction with this change because it requires updating existing tests that depend on the checkbox aria-label selector. Future tests would also need to align with this change.
There was an initial attempt to land a similar change but then that got reverted in here - https://chromium-review.googlesource.com/c/devtools/devtools-frontend/+/3948895
Nonetheless, having both an aria-label on the checkbox and the for attribute on the label will cause the duplicate screen reader announcement.
Bug: 1414952
Change-Id: If4342b860d07b0a9494b90f4d15cb99688cb9d6e
Reviewed-on: https://chromium-review.googlesource.com/c/devtools/devtools-frontend/+/4591249
Reviewed-by: Alex Rudenko <alexrudenko@chromium.org>
Commit-Queue: Sylvester Elorm Coch <elormcoch@microsoft.com>
diff --git a/front_end/ui/legacy/UIUtils.ts b/front_end/ui/legacy/UIUtils.ts
index 8cb0ce8..60ead61 100644
--- a/front_end/ui/legacy/UIUtils.ts
+++ b/front_end/ui/legacy/UIUtils.ts
@@ -1201,7 +1201,7 @@
element.checkboxElement.checked = Boolean(checked);
if (title !== undefined) {
element.textElement.textContent = title;
- ARIAUtils.setAccessibleName(element.checkboxElement, title);
+ element.checkboxElement.title = title;
if (subtitle !== undefined) {
element.textElement.createChild('div', 'dt-checkbox-subtitle').textContent = subtitle;
}
diff --git a/test/e2e/application/cookies_test.ts b/test/e2e/application/cookies_test.ts
index 1e8052a..3a9f226 100644
--- a/test/e2e/application/cookies_test.ts
+++ b/test/e2e/application/cookies_test.ts
@@ -119,7 +119,7 @@
return previewValue === 'Hello%2BWorld!';
});
- await click('[aria-label="Show URL-decoded"]');
+ await click('[title="Show URL-decoded"]');
await waitForFunction(async () => {
const previewValueNode = await waitFor('.cookie-preview-widget-cookie-value');
diff --git a/test/e2e/application/storage_test.ts b/test/e2e/application/storage_test.ts
index a0ea47c..336ce24 100644
--- a/test/e2e/application/storage_test.ts
+++ b/test/e2e/application/storage_test.ts
@@ -20,7 +20,7 @@
const COOKIES_SELECTOR = '[aria-label="Cookies"].parent';
const STORAGE_SELECTOR = '[aria-label="Storage"]';
const CLEAR_SITE_DATA_BUTTON_SELECTOR = '#storage-view-clear-button';
-const INCLUDE_3RD_PARTY_COOKIES_SELECTOR = '[aria-label="including third-party cookies"]';
+const INCLUDE_3RD_PARTY_COOKIES_SELECTOR = '[title="including third-party cookies"]';
let DOMAIN_SELECTOR: string;
diff --git a/test/e2e/helpers/console-helpers.ts b/test/e2e/helpers/console-helpers.ts
index 05ceabcd..88f78cf 100644
--- a/test/e2e/helpers/console-helpers.ts
+++ b/test/e2e/helpers/console-helpers.ts
@@ -40,9 +40,9 @@
export const CONSOLE_MESSAGE_WRAPPER_SELECTOR = '.console-group-messages .console-message-wrapper';
export const CONSOLE_SELECTOR = '.console-user-command-result';
export const CONSOLE_SETTINGS_SELECTOR = '[aria-label^="Console settings"]';
-export const AUTOCOMPLETE_FROM_HISTORY_SELECTOR = '[aria-label^="Autocomplete from history"]';
-export const SHOW_CORS_ERRORS_SELECTOR = '[aria-label^="Show CORS errors in console"]';
-export const LOG_XML_HTTP_REQUESTS_SELECTOR = 'input[aria-label="Log XMLHttpRequests"]';
+export const AUTOCOMPLETE_FROM_HISTORY_SELECTOR = '[title="Autocomplete from history"]';
+export const SHOW_CORS_ERRORS_SELECTOR = '[title="Show CORS errors in console"]';
+export const LOG_XML_HTTP_REQUESTS_SELECTOR = '[title="Log XMLHttpRequests"]';
export const CONSOLE_CREATE_LIVE_EXPRESSION_SELECTOR = '[aria-label^="Create live expression"]';
export const Level = {
diff --git a/test/e2e/helpers/elements-helpers.ts b/test/e2e/helpers/elements-helpers.ts
index c007b58..577a1a6 100644
--- a/test/e2e/helpers/elements-helpers.ts
+++ b/test/e2e/helpers/elements-helpers.ts
@@ -32,8 +32,8 @@
const CSS_STYLE_RULE_SELECTOR = '[aria-label*="css selector"]';
const COMPUTED_PROPERTY_SELECTOR = 'devtools-computed-style-property';
const COMPUTED_STYLES_PANEL_SELECTOR = '[aria-label="Computed panel"]';
-const COMPUTED_STYLES_SHOW_ALL_SELECTOR = '[aria-label="Show all"]';
-const COMPUTED_STYLES_GROUP_SELECTOR = '[aria-label="Group"]';
+const COMPUTED_STYLES_SHOW_ALL_SELECTOR = '[title="Show all"]';
+const COMPUTED_STYLES_GROUP_SELECTOR = '[title="Group"]';
const ELEMENTS_PANEL_SELECTOR = '.panel[aria-label="elements"]';
const FONT_EDITOR_SELECTOR = '[aria-label="Font Editor"]';
const HIDDEN_FONT_EDITOR_SELECTOR = '.font-toolbar-hidden';
@@ -740,7 +740,7 @@
const initialValue = await getContentOfSelectedNode();
const classesPane = await waitFor(CLS_PANE_SELECTOR);
- await click(`input[aria-label="${checkboxLabel}"]`, {root: classesPane});
+ await click(`[title="${checkboxLabel}"]`, {root: classesPane});
await waitForSelectedNodeChange(initialValue);
};
diff --git a/test/e2e/helpers/issues-helpers.ts b/test/e2e/helpers/issues-helpers.ts
index 6a87e67..c1aa67f 100644
--- a/test/e2e/helpers/issues-helpers.ts
+++ b/test/e2e/helpers/issues-helpers.ts
@@ -20,8 +20,8 @@
export const CATEGORY = '.issue-category:not(.hidden-issues)';
export const KIND = '.issue-kind';
export const CATEGORY_NAME = '.issue-category .title';
-export const CATEGORY_CHECKBOX = 'input[aria-label="Group by category"]';
-export const KIND_CHECKBOX = 'input[aria-label="Group by kind"]';
+export const CATEGORY_CHECKBOX = '[title^="Group displayed issues under"]';
+export const KIND_CHECKBOX = '[title^="Group displayed issues as"]';
export const ISSUE = '.issue:not(.hidden-issue)';
export const ISSUE_TITLE = '.issue .title';
export const AFFECTED_ELEMENT_ICON = '.affected-resource-csp-info-node';
diff --git a/test/e2e/helpers/memory-helpers.ts b/test/e2e/helpers/memory-helpers.ts
index 6ca07ec..7ddb037 100644
--- a/test/e2e/helpers/memory-helpers.ts
+++ b/test/e2e/helpers/memory-helpers.ts
@@ -45,7 +45,7 @@
const radioButton = await $('//label[text()="Allocation instrumentation on timeline"]', undefined, 'xpath');
await clickElement(radioButton);
if (recordStacks) {
- await click('input[aria-label="Record stack traces of allocations (extra performance overhead)"]');
+ await click('[title="Record stack traces of allocations (extra performance overhead)"]');
}
await click('button[aria-label="Start recording heap profile"]');
await new Promise(r => setTimeout(r, 200));
diff --git a/test/e2e/helpers/network-helpers.ts b/test/e2e/helpers/network-helpers.ts
index 6d8765f..2087863 100644
--- a/test/e2e/helpers/network-helpers.ts
+++ b/test/e2e/helpers/network-helpers.ts
@@ -93,11 +93,11 @@
}
export async function setPersistLog(persist: boolean) {
- await setCheckBox('[aria-label="Preserve log"]', persist);
+ await setCheckBox('[title="Do not clear log on page reload / navigation"]', persist);
}
export async function setCacheDisabled(disabled: boolean): Promise<void> {
- await setCheckBox('[aria-label="Disable cache"]', disabled);
+ await setCheckBox('[title^="Disable cache"]', disabled);
}
export async function setTimeWindow(): Promise<void> {
diff --git a/test/e2e/helpers/settings-helpers.ts b/test/e2e/helpers/settings-helpers.ts
index 3070b22..99624e0 100644
--- a/test/e2e/helpers/settings-helpers.ts
+++ b/test/e2e/helpers/settings-helpers.ts
@@ -81,7 +81,7 @@
const enabledPattern = '.ignore-list-options:not(.ignore-listing-disabled)';
const disabledPattern = '.ignore-list-options.ignore-listing-disabled';
await waitFor(enable ? disabledPattern : enabledPattern);
- await click('[aria-label="Enable Ignore Listing"]');
+ await click('[title="Enable Ignore Listing"]');
await waitFor(enable ? enabledPattern : disabledPattern);
await closeSettings();
};
diff --git a/test/e2e/host/user-metrics_test.ts b/test/e2e/host/user-metrics_test.ts
index 20c5a42..83340b9 100644
--- a/test/e2e/host/user-metrics_test.ts
+++ b/test/e2e/host/user-metrics_test.ts
@@ -313,7 +313,7 @@
it('dispatches an event when experiments are enabled and disabled', async () => {
await openSettingsTab('Experiments');
- const customThemeCheckbox = await waitFor('[aria-label="Allow extensions to load custom stylesheets"]');
+ const customThemeCheckbox = await waitFor('[title="Allow extensions to load custom stylesheets"]');
// Enable the experiment
await customThemeCheckbox.click();
// Disable the experiment
diff --git a/test/e2e/issues/issue-view-caching_test.ts b/test/e2e/issues/issue-view-caching_test.ts
index 022c718..74bb7fe 100644
--- a/test/e2e/issues/issue-view-caching_test.ts
+++ b/test/e2e/issues/issue-view-caching_test.ts
@@ -85,7 +85,7 @@
'false',
];
await waitForResources(2, [header, expectedRow1, expectedRow2]);
- await setCheckBox('[aria-label="Include third-party cookie issues"]', true);
+ await setCheckBox('[title="Include cookie Issues caused by third-party sites"]', true);
// Trigger issue again to see if resources are updated.
await triggerIssue();
diff --git a/test/e2e/network/network-datagrid_test.ts b/test/e2e/network/network-datagrid_test.ts
index 2bf95e7..3f38fb3 100644
--- a/test/e2e/network/network-datagrid_test.ts
+++ b/test/e2e/network/network-datagrid_test.ts
@@ -65,9 +65,9 @@
await navigateToNetworkTab('resources-from-cache.html');
// Click the label next to the checkbox input element
- await click('[aria-label="Disable cache"] + label');
+ await click('[title^="Disable cache"] + label');
- const checkbox = await waitFor('[aria-label="Disable cache"]');
+ const checkbox = await waitFor('[title^="Disable cache"]');
const checked = await checkbox.evaluate(box => (box as HTMLInputElement).checked);
assert.strictEqual(checked, false, 'The disable cache checkbox should be unchecked');
diff --git a/test/e2e/rendering/Rendering_test.ts b/test/e2e/rendering/Rendering_test.ts
index 6352ae2..25b790d 100644
--- a/test/e2e/rendering/Rendering_test.ts
+++ b/test/e2e/rendering/Rendering_test.ts
@@ -4,7 +4,7 @@
import {assert} from 'chai';
-import {getBrowserAndPages, waitFor, waitForAria} from '../../shared/helper.js';
+import {getBrowserAndPages, waitFor} from '../../shared/helper.js';
import {describe, it} from '../../shared/mocha-extensions.js';
import {openPanelViaMoreTools} from '../helpers/settings-helpers.js';
@@ -76,6 +76,6 @@
it('includes UI for emulating auto dark mode', async () => {
await openPanelViaMoreTools('Rendering');
- await waitForAria('Enable automatic dark mode[role="checkbox"]');
+ await waitFor('[title="Enable automatic dark mode"]');
});
});
diff --git a/test/e2e/sources/breakpoint-csp-violations_test.ts b/test/e2e/sources/breakpoint-csp-violations_test.ts
index 86c64c1..f2a59ed 100644
--- a/test/e2e/sources/breakpoint-csp-violations_test.ts
+++ b/test/e2e/sources/breakpoint-csp-violations_test.ts
@@ -13,7 +13,7 @@
await openSourcesPanel();
await waitForAria('CSP Violation Breakpoints');
await click('[aria-label="CSP Violation Breakpoints"]');
- await click('[aria-label="Trusted Type Violations"]');
+ await click('[title="Trusted Type Violations"]');
await click(PAUSE_ON_UNCAUGHT_EXCEPTION_SELECTOR);
const resource = goToResource('network/trusted-type-violations-enforced.rawresponse');
@@ -37,7 +37,7 @@
await openSourcesPanel();
await waitForAria('CSP Violation Breakpoints');
await click('[aria-label="CSP Violation Breakpoints"]');
- await click('[aria-label="Trusted Type Violations"]');
+ await click('[title="Trusted Type Violations"]');
const resource = goToResource('network/trusted-type-violations-report-only.rawresponse');
diff --git a/test/unittests/front_end/panels/application/StorageView_test.ts b/test/unittests/front_end/panels/application/StorageView_test.ts
index 4c30e55..063bdba 100644
--- a/test/unittests/front_end/panels/application/StorageView_test.ts
+++ b/test/unittests/front_end/panels/application/StorageView_test.ts
@@ -76,7 +76,7 @@
assertElement(container, HTMLDivElement);
assertShadowRoot(container.shadowRoot);
const customQuotaCheckbox = container.shadowRoot.querySelector('.quota-override-row span')
- ?.shadowRoot?.querySelector('[aria-label="Simulate custom storage quota"]') ||
+ ?.shadowRoot?.querySelector('[title="Simulate custom storage quota"]') ||
null;
assertElement(customQuotaCheckbox, HTMLInputElement);
customQuotaCheckbox.checked = true;