playwright
playwright copied to clipboard
[Feature] locator for getting an element's description
I'm testing an error message.
Here's what I have to do now:
const subjectDescriptionId = await page
.getByRole('textbox', {name: /subject/i})
.getAttribute('aria-describedby')
await expect(page.locator(`id=${subjectDescriptionId}`)).toHaveText(/required/i)
Here's what I'd like to be able to do:
const subjectDescription = await page
.getByRole('textbox', {name: /subject/i})
.getDescription()
await expect(subjectDescription).toHaveText(/required/i)
Thanks!
Is there something similar in testing library?
I like the feature request to get the aria describedby element!
Unfortunately we don't have anything like this in Testing Library either. Definitely would be a good one to have though.
Why was this issue closed?
Thank you for your involvement. This issue was closed due to limited engagement (upvotes/activity), lack of recent activity, and insufficient actionability. To maintain a manageable database, we prioritize issues based on these factors.
If you disagree with this closure, please open a new issue and reference this one. More support or clarity on its necessity may prompt a review. Your understanding and cooperation are appreciated.
This is one of those things people don't know they need (hence the lower up-votes) but will use once they have.
i think it would be useful to get the element's description, but also to locate by description. similar to getByLabel a getByDescription would be useful
@testing-library/jest-dom has three matchers specifically for accessibility testing:
- https://github.com/testing-library/jest-dom#tohaveaccessibledescription
- https://github.com/testing-library/jest-dom#tohaveaccessibleerrormessage
- https://github.com/testing-library/jest-dom#tohaveaccessiblename
We've been using this API extensively, and would be happy to have this or something similar with Playwright, too!
This would be very nice to have in playwright!
Upvoting this since I'm running into a situation where having this feature would save me some time.
I would also find this quite useful. We are using Playwright for testing our UI pretty extensively, and this is very helpful for ensuring that the screen reader experience is tested automatically (to some extent).
Having locators for getting an element's accessible description, as well as selecting elements by an accessible description would be great. In addition to what Kent proposed above, something like:
// Test that there is an element describing field in the page
const description = await page.getByDescription(/required/i)
await expect(description).toBeVisible()
Hello @dgozman . I've taken a look and started thinking what could be a possible solution.
Locators βlocateβ with selector strings. There are util functions to construct the selector string that can be found in locatorUtils.ts file. These selectors seem that they are some internal selectors because they are prefixed with internal: keyword. I suppose we should come up with an internal selector for the accessible description but I haven't found a comprehensive doc on these internal selectors.
If the usage of internal selector is not obligatory, an xpath selector like the following can work:
//[@id=//[@aria-describedby]/@aria-describedby]
It looks for elements that are the accessible descriptions of an other element.
I would be happy ro receive some help
I think it's worth pointing out that aria-describedby can point to multiple elements according to the spec. If that doesn't add enough complexity into the mix, another thing to consider is that a "descriptor element" can choose to expose only some of its content. Consider the following markup:
<h1 aria-describedby="description">ZA WARUDO</h1>
<div id="description">
<div aria-hidden="true">Bro... you're a weeb.</div>
<div>Apparently, some entity that can stop time.</div>
</div>
My understanding is that the accessible description for the h1 tag here should be
Apparently, some entity that can stop time.
not
Bro... you're a weeb. Apparently, some entity that can stop time.
The Google Chrome Devtools seem to agree with this, as does MacOS VoiceOver (again, used on a webpage in Chrome).
How would a getDescription function work here? Would it grab #description? Would it grab only #description > :nth-child(2)? What if #description had even more elements that are exposed to the a11y tree? I'm aware that the example above is rather contrived. But what I'm trying to point out is that it might not be so ideal, easy, or clear to create a getDescription function. It would be hard to know what is "correct" to get, and it would be even harder to know what every developer would truly want.
I'm assuming that we're all here because what we really want is to write tests which verify that our elements have accessible descriptions. To that end, I think it would be better to have an expect(locator).toHaveAccessibleDescription() assertion like Jest DOM Testing Library. And I'm assuming that this would be easier to write than getDescription. Under the hood, they seem to be using the dom-accessibility-api package. Sebastian Silbermann might have insights as a maintainer on what Playwright can do here. (I'm trying to avoid unnecessary pings. The Playwright team can ping him if they consider it worthwhile.)
In the meantime, for those of you who absolutely need a way to have Jest DOM Testing Library's toHaveAccessibleDescription in Playwright, there's an alternative that I'm using temporarily. It isn't great. But it allows me to get the job done. And I have more confidence in computeAccessibleDescription than I do in creating something myself. (Remember that Jest DOM Testing Library uses this function under the hood.)
/* ---------- Utils File ---------- */
/** Retrieves the [accessible description](https://developer.mozilla.org/docs/Web/Accessibility/ARIA/Attributes/aria-describedby) for an element. */
async function getAccessibleDescriptionFor(element: Locator): Promise<string> {
const page = element.page();
const a11yHelperLoaded = await page.evaluate(() => "computeAccessibleDescription" in window);
if (!a11yHelperLoaded) {
await page.addScriptTag({
type: "module",
content: `
import { computeAccessibleDescription } from "https://cdn.jsdelivr.net/npm/[email protected]/+esm";
window.computeAccessibleDescription = computeAccessibleDescription;
`,
});
await page.waitForFunction("'computeAccessibleDescription' in window");
}
// Note: TS users will need a Type Cast here
return element.evaluate((node) => window.computeAccessibleDescription(node));
}
/* ---------- Within a Test File ---------- */
const email = page.getByRole("textbox", { name: /email/i });
const button = page.getByRole("button", { name: /submit/i });
// Ban invalid emails
await expect(email).toHaveValue("");
await button.click();
await expect(email).toHaveAttribute("aria-invalid", String(true));
expect(await getAccessibleDescriptionFor(email)).toBe("Email is invalid");
// Allow valid emails
await email.fill("[email protected]");
await button.click();
await expect(email).toHaveAttribute("aria-invalid", String(false));
expect(await getAccessibleDescriptionFor(email)).toBe("");
I'm doing things this way because exposeFunction seemed to break computeAccessibleDescription in some way that I couldn't understand. (And I didn't bother diving too deep into how to solve that.)
Obviously, there are limitations to this approach. For instance, if you're using CSPs, you'll need a workound. I'm sure there are ways to improve this approach; so feel free to make improvements if possible. But if this is feasible for you for now... that's great.
For the Playwright maintainers: The computeAccessibleDescription helper in the dom-accessibility-api NPM package might be helpful as a start (if it's of interest). I haven't scrutinized it too much.
Hooray! Thank you ππ
Looking at the comments in this GitHub Issue, it looks like we have a lot of exciting assertions incoming!
toHaveAccessibleNametoHaveAccessibleDescriptiontoHaveRole
Thanks so much for this! And thanks for exposing an assertion instead of a complex locator. ππΎββοΈ
Really looking forward to this.