playwright
playwright copied to clipboard
[Feature] Aria methods and assertions
Originally posted in https://github.com/microsoft/playwright/pull/12999#issuecomment-1092536668.
Now that we have all these accessible attributes helpers, is it worth making them available under locator and assertions as well?
For instance, we want to know whether an element is expanded or not:
const button = page.locator('role=button[name=Expand me]'); const isExpanded = await button.expanded(); // `true` or `false` await expect(button).toBeExpanded(); // Error if the button is not expandedI think it's only natural to make them available both via querying and accessing.
Currently, we only have
page.accessibility, but that uses browsers' API and it's platform-specific, different from the JS implementation injected into the browsers.
It'll be great to add support for computing aria attributes on Locator and ElementHandle to make them more feature-complete.
Proposed methods on Locator (and ElementHandle):
- [ ]
locator.ariaRole(): string: Get the aria role of the element. - [ ]
locator.ariaName(): string: Get the aria name of the element. - [x]
locator.isChecked(): boolean: Already available. - [ ]
locator.isSelected(): boolean: Returns whether the option is selected. - [ ]
locator.isExpanded(): boolean: Returns whether the element is expanded. - [ ]
locator.isPressed(): boolean: Returns whether the button is being pressed. - [x]
locator.isDisabled(): boolean: Already available. - [ ]
locator.level(): number: Get the level of the heading element.
And for assertions:
- [ ]
expect(locator).toHaveRole(role: string): Test whether the element has the aria role. - [ ]
expect(locator).toHaveName(name: string | RegExp): Test whether the element has the aria name. - [x]
expect(locator).toBeChecked(): Already available. - [ ]
expect(locator).toBeSelected(): Expect the option to be selected. - [ ]
expect(locator).toBeExpanded(): Expect the element to be expanded. - [ ]
expect(locator).toBePressed(): Expect the button to be pressed. - [x]
expect(locator).toBeDisabled(): Already available. - [ ]
expect(locator).toHaveLevel(level: number): Expect the heading element to have the level.
c.c. @dgozman
It would be nice to have the accessible name selector as an option to filter, like hasText. It would look like this:
await page.locator('role=button', { hasName: /click me/i }).click();
// translates to
await page.locator('role=button[name=/click me/i]).click();
Another idea; select by aria description:
await page.locator('role=article[description=/something/i]');
I can create separate issues if needed.
I'd find assertions for aria-invalid and aria-errormessage useful, maybe toBeValid/toBeInvalid and toHaveErrorMessage?
I get around the former with https://github.com/PREreview/prereview.org/blob/403705b239fe088d7d2ed28e46042ed7f94324cb/integration/posting-a-prereview.spec.ts#L932, but the latter is harder as it refers to another element.
With the release of 1.27.0 my first issue is fixed, thanks for landing it! The second issue persists, however, I still can't query with accessible description using accessible selectors. @thewilkybarkid's issue is not fixed either, as far as I can tell.
It would be nice to have an assertion that can compute and compare the accessible name and description for an element, similar to the jest-dom APIs:
https://github.com/testing-library/jest-dom/blob/main/src/to-have-accessible-name.js https://github.com/testing-library/jest-dom/blob/main/src/to-have-accessible-description.js
The W3C Spec for the computation is here: https://www.w3.org/TR/accname-1.1/#mapping_additional_nd_te
Since Playwright also has access to browser internals it may be possible to turn on the AccessibilityObjectModel (AOM) experimental features, although there is spotty support for the features you'd need at the moment. https://github.com/WICG/aom
Actually, to solve this problem, I have added the following code:
import { expect, Locator } from '@playwright/test'
// We detected that not all aria labels are implemented in Playwright. https://github.com/microsoft/playwright/issues/13517
// This is currently the only attributes tested. You could add more if needed.
type ariaAttributesSupported = 'selected'|'other'
export async function isAriaAttributeValue (locator:Locator, label:ariaAttributesSupported, value: string) {
const currentValue = await locator.getAttribute(`aria-${label}`)
const res = currentValue === value
return res
}
export async function expectToHaveAriaAttributeValue (locator:Locator, label:ariaAttributesSupported, value: string, options?: Record<string, unknown>) {
return expect(locator).toHaveAttribute(`aria-${label}`, value, options)
}
I'd find assertions for
aria-invalidandaria-errormessageuseful, maybetoBeValid/toBeInvalidandtoHaveErrorMessage?I get around the former with https://github.com/PREreview/prereview.org/blob/403705b239fe088d7d2ed28e46042ed7f94324cb/integration/posting-a-prereview.spec.ts#L932, but the latter is harder as it refers to another element.
The browser accessibility tree will expose an "Invalid user entry" property which is true either if an input is invalid from native constraints validation or from aria-invalid, similar to checked vs aria-checked. toBeValid and toBeInvalid matcher would be very useful to be able to write tests that work with either.
E.g. all of these should trigger toBeInvalid():
<input type="radio" aria-invalid">
<input type="radio" required>
input.setCustomValidity('error');
Example browser a11y properties
Role: radio
Invalid user entry: false
Focusable: true
Checked: true
Labeled by: label
Now that webdriver includes getComputedRole and getComputedLabel is there any chance that Playwright could support those? Providing access to the browser internal name and role would allow a huge increase in accessibility tests we could write.
@dgozman Is there a timeline for the following matchers?
toHaveNametoHaveRoletoHaveDescription
At Teams we've integrated axe-core already, but we would like more level of granularity to assert specific scenarios that should not regress. To be able to assert accessible name and role explicitly would be really helpful in tests for keyboard navigation where you'd want to press keys and also assert that each focus stop has correct name and role.
An example would be a complex UI component like a treeview, where keyboard navigation patterns can be really complex with lots of extra embedded controls like tooltips and flyout menus, we need to make sure we can have that level of granularity
See also #18332 for a more specific issue covering toHaveAccessibleName and toHaveAccessibleDescription.