Test ARIA label attributes
All interactive elements must have an accessible name that describes their purpose. Screen readers announce this name to users, making it essential for navigation.
Icon-only buttons
Icon-only buttons:
Close button (common issue):
Sample dialog content
Form with labelledby:
What users hear
Edit button:
"button"
Delete button:
"button"
Close button:
"times, button"
Search input:
"search input"
Accessible vs inaccessible
<!-- No accessible name --> <button>✏️</button> <button>🗑️</button> <button>✕</button> <!-- Input without label association --> <h3>Search Products</h3> <input type="search" />
<!-- Using aria-label -->
<button aria-label="Edit document">✏️</button>
<button aria-label="Delete document">🗑️</button>
<button aria-label="Close dialog">✕</button>
<!-- Using aria-labelledby -->
<h3 id="search-heading">Search Products</h3>
<input type="search"
aria-labelledby="search-heading" />Testing hints
import AxeBuilder from '@axe-core/playwright';
// Check for missing accessible names
const results = await new AxeBuilder({ page })
.withRules(['button-name', 'label'])
.analyze();
// Find buttons without labels
const violations = results.violations.filter(
v => v.id === 'button-name'
);
console.log('Buttons without names:', violations);
// Verify specific button has aria-label
const editBtn = page.getByRole('button', {
name: 'Edit document'
});
await expect(editBtn).toBeVisible();// Get all buttons and check for names
const buttons = await page.$$('button');
for (const button of buttons) {
const ariaLabel = await button.getAttribute('aria-label');
const textContent = await button.textContent();
const ariaLabelledBy = await button.getAttribute(
'aria-labelledby'
);
const hasName = ariaLabel ||
textContent?.trim() ||
ariaLabelledBy;
if (!hasName) {
console.log('Button without accessible name:',
await button.evaluate(el => el.outerHTML)
);
}
}Automation hints
page.getByRole('button', { name: 'Edit document' })getAttribute('aria-label')page.locator('#search-heading')