Using Testing Library with implicit ARIA roles

Writing tests that interact with screen elements through their roles is a good strategy to ensure the proper semantics of an HTML document.

Regarding structure and accessibility, it's essential that the page headline has a heading role, that a select has a combobox—or listbox—role, that a textarea has a textbox role, and so on. However, when the aria-role attribute is not explicitly set to an element, it's not easy to find out which role is implicitly associated with that.

The following table presents the roles that are implicitly tied to several elements commonly used in an HTML document:

ElementVariationRole
awith hreflink
awithout hrefgeneric
articlearticle
asidecomplementary
blockquoteblockquote
buttonbutton
captioncaption
codecode
datalistlistbox
detailsgroup
dialogdialog
formform
h1–h6heading
hrseparator
htmldocument
imgwith alt="some text"img
imgwith alt=""presentation
imgwithout altimg
inputtype: buttonbutton
inputtype: checkboxcheckbox
inputtype: emailtextbox
inputtype: numberspinbutton
inputtype: rangeslider
inputtype: searchsearchbox
inputtype: teltextbox
inputtype: texttextbox
inputtype: urltextbox
inputtype: text, search, tel, url, email with list attributecombobox
lilistitem
menulist
metermeter
navnavigation
ollist
optgroupgroup
optionoption
outputoutput
pparagraph
progressprogressbar
selectcombobox
selectwith multiplelistbox
tabletable
tbody, tfoot, theadrowgroup
timetime
tdwithin role=table ancestorcell
tdwithin role=grid or role=treegrid ancestorgridcell
thwithin role=table ancestorcolumnheader, rowheader or cell
thwithin role=grid or role=treegrid ancestorcolumnheader, rowheader or gridcell
trrow
ullist

Knowing the respective implicit role of each HTML element makes the use of Testing Library's getByRole function easy when you want to get them by their roles:

it('should contain a link to the blog', () => {
  const { getByRole } = render(<MyCustomComponent />);
  const link = getByRole('link', { name: 'Blog' });
  expect(link).toHaveAttribute(
    'href',
    'https://rafaelcamargo.com/blog/l/en-US/'
  );
});

Important to notice that, depending on the number of elements rendered on the screen, getting elements by their roles can slow down the test execution. To avoid possible performance issues, you can drastically reduce the area scanned by the getByRole function by using the helper function within:

it('should contain a link to the blog', () => {
  const { container } = render(<MyCustomComponent />);
  const section = container.querySelector('#linkSection');
  const link = within(section).getByRole('link', { name: 'Blog' });
  expect(link).toHaveAttribute(
    'href',
    'https://rafaelcamargo.com/blog/l/en-US/'
  );
});

In case you are considering making those implicit role values explicit by setting aria-role attributes to those elements, be aware it is not recommended. To view the full list of HTML elements and their respective implicit roles, visit the ARIA official documentation.