Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Feature] locator for getting an element's description #18332

Closed
kentcdodds opened this issue Oct 25, 2022 · 14 comments · Fixed by #30555
Closed

[Feature] locator for getting an element's description #18332

kentcdodds opened this issue Oct 25, 2022 · 14 comments · Fixed by #30555
Assignees
Labels

Comments

@kentcdodds
Copy link

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!

@mxschmitt
Copy link
Member

Is there something similar in testing library?

I like the feature request to get the aria describedby element!

@kentcdodds
Copy link
Author

Unfortunately we don't have anything like this in Testing Library either. Definitely would be a good one to have though.

@pavelfeldman
Copy link
Member

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.

@kentcdodds
Copy link
Author

This is one of those things people don't know they need (hence the lower up-votes) but will use once they have.

@WickyNilliams
Copy link

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

@lukasgebhard
Copy link

@testing-library/jest-dom has three matchers specifically for accessibility testing:

  1. https://github.com/testing-library/jest-dom#tohaveaccessibledescription
  2. https://github.com/testing-library/jest-dom#tohaveaccessibleerrormessage
  3. 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!

@kristojorg
Copy link
Contributor

This would be very nice to have in playwright!

@trieu-h
Copy link

trieu-h commented Nov 3, 2023

Upvoting this since I'm running into a situation where having this feature would save me some time.

@camchenry
Copy link

camchenry commented Jan 12, 2024

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()

@stchristian
Copy link

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

@ITenthusiasm
Copy link

ITenthusiasm commented Feb 12, 2024

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.

@dgozman dgozman added v1.44 and removed v1.43 labels Mar 25, 2024
yury-s pushed a commit that referenced this issue Apr 18, 2024
dgozman added a commit that referenced this issue Apr 19, 2024
…30434)

There are new "non-manual" WPT accname tests that we now mostly pass,
which required a few tweeks in calculating role and name.

Also implemented accessible description computation, which is just a
small addition on top of accessible name, and passed respective wpt
tests.

References #18332.
@kentcdodds
Copy link
Author

Hooray! Thank you 👏👏

@ITenthusiasm
Copy link

Looking at the comments in this GitHub Issue, it looks like we have a lot of exciting assertions incoming!

  • toHaveAccessibleName
  • toHaveAccessibleDescription
  • toHaveRole

Thanks so much for this! And thanks for exposing an assertion instead of a complex locator. 🙇🏾‍♂️

@MattyBalaam
Copy link

Really looking forward to this.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging a pull request may close this issue.