skip to content

In an end-to-end browser test, why do many teams locate elements by accessible role and visible name — for example a button named "Save changes" — before trying any other strategy, and where does that strategy break down?

level: middleimportance: must knowfreq 64%

answer

  1. identity a user actually perceives
  2. refactor moves markup, not meaning
  3. not found often means not labelled
  4. role names the kind, not the instance
  5. name comes from text, so copy moves it

basics

~20 s

Role plus name is how a user identifies a control, so it changes only when the interface genuinely changes, and a test that cannot find the element usually reveals a real labelling gap. It breaks on unlabelled icon controls, non-semantic markup, duplicated names, and copy or locale churn.

solid answer

~50 s

Locating by role and name buys two things. First, durability: it is anchored on what the element *is* and what it *says*, so wrapper divs, class renames and CSS-in-JS hashes cannot touch it. Second, feedback: if `getByRole('button', { name: 'Save changes' })` finds nothing, the usual cause is a product defect — an icon-only button with no label, or a clickable `div` that is not a button at all — so the test surfaces a real problem instead of hiding it behind a test id. The limits are equally concrete. Non-semantic custom widgets expose no useful role; icon controls with no `aria-label` have no accessible name; several controls on one page share the name "Save", so you must scope to a container; and because the name comes from visible text, it moves when copy or locale moves. Fix the app where you can, and fall back to an owned `data-testid` where you cannot.

go deeper

for a junior

Recall that a control's role is what it is (button, link, textbox) and its name is the label a user reads, and that tests should look elements up that way.

for a middle

Explain where the accessible name comes from — visible text, a label element, or aria-label — and give the concrete failure modes: no role, no name, and duplicate names on one page.

for a senior

Demonstrate the diagnostic habit: when a role-based locator fails, decide whether the app regressed before changing the test, and be able to say what a test id buys and what it silences.

for a principal

Argue the policy: making labelled, semantic controls a definition-of-done item is what makes a role-first convention affordable across teams, and set the escape hatch for third-party markup explicitly rather than leaving it to each author.

## The claim being made "Query by role and accessible name first" is the default advice in the Testing Library family and in modern e2e locator APIs alike. It is worth understanding *why*, because the reasoning transfers to whatever tool a team is using next year. A **role** is what kind of control an element is — button, link, textbox, checkbox, heading, dialog. An **accessible name** is the short label that identifies that specific instance: usually its visible text, otherwise a label element or an `aria-label`. Together they are the identity a user works with: "the *Save changes* button." Nobody clicks the third child of the second div. ## Benefit one: it changes only when the interface changes Refactors move markup; they are not supposed to move meaning. Wrapping content for layout, renaming a class, swapping a styling library, or extracting a subcomponent all leave a button that is still a button and still says "Save changes." A locator anchored on role and name is therefore immune to the whole class of failures that structural CSS paths suffer. It cuts the other way too, and that is a feature: if someone replaces `<button>Save changes</button>` with `<div class="btn" onclick=…>`, the locator fails — and it *should*, because keyboard and assistive-technology users just lost the control. ## Benefit two: a free reachability signal This is the part candidates usually miss. Failing to find an element by role and name is diagnostic. The common causes are all real defects: - an icon-only button with no `aria-label`, so it has no name at all; - an input with a floating placeholder but no associated `<label>`, so it has no name; - a `div` with a click handler, so it has no role; - a control hidden from the accessibility tree by `aria-hidden` while still visible. A test id would sail past every one of those. That does not make test ids wrong — it makes them silent, which is a cost you should pay knowingly. This is a *light* signal, not an accessibility audit; systematic accessibility assertions are a separate practice. ## Where it breaks down **No role.** Rich custom widgets — drag handles, canvas-rendered charts, virtualised grids, third-party embeds you cannot edit — often expose nothing meaningful. Query them by a container test id. **No name.** An icon button whose only content is an `<svg>` has no accessible name. The right first move is to fix the app; the right second move, when the markup is not yours, is a test id. **Ambiguous names.** A page listing ten orders has ten buttons named "Cancel". Role and name identify the *kind* of control, not the *instance*, so you scope to a container that identifies the instance and query within it. **Name churn.** The name is derived from visible text, so a copy edit or a localised build moves it. That is not an argument against role-based locating — it is an argument for deciding deliberately which locale the suite runs in and which tests are allowed to depend on copy. **Name computation surprises.** The name may come from a `<label>`, `aria-label`, or `aria-labelledby` rather than from the text you see, and matching is normally whitespace-normalised, which trips people up when text is split across elements or padded by icons. ## The practical strategy Use a ladder, and apply it per element rather than per suite: 1. Role and accessible name where the element is a real, labelled control. 2. A component-owned `data-testid` where semantics genuinely do not identify it, or where it identifies an *instance* (a specific row). 3. Structural CSS only inside markup you do not control. ```js // identity a user would recognise await page.getByRole('button', { name: 'Save changes' }).click(); // instance identity: scope first, then use role inside const row = page.getByTestId('order-row-1042'); await row.getByRole('button', { name: 'Cancel' }).click(); ``` The mature position is not "role good, test id bad." It is that role and name are the *default* because they are anchored on user-perceivable identity and give feedback for free, and that every fallback should be a conscious note that this element could not be identified the way a user identifies it.

  • A designer ships an icon-only delete button and your role-and-name locator cannot find it. What do you do?
    Treat it as a product bug first: the control has no accessible name, so screen-reader users hear nothing useful either. Ask for a visually hidden label or an `aria-label`, which fixes the app and the test in one change. Reach for a `data-testid` only if the markup belongs to a third party you cannot change.
  • Does querying by role make component and e2e tests slower?
    Role resolution does more work than a raw CSS match, but in a browser test the cost is invisible next to navigation, network and rendering. Suite runtime is dominated by waiting, not by locator strategy, so trading a few milliseconds for selectors that survive refactors is an easy call.
  • Isn't querying by role just duplicating the accessibility tests?
    No. It reuses the accessibility tree as an addressing scheme and only tells you an element is identifiable. It says nothing about contrast, focus order, or landmark structure. Dedicated accessibility checks still have to exist; role-based locating just stops your functional tests from silently working on an unusable interface.

saying these in an interview costs you the question

  • Adds a test id immediately rather than asking why the name is missing
  • Thinks role and name uniquely identify an element on any page
  • Assumes the accessible name is always the element's inner text
  • Claims role-based queries replace accessibility testing
  • Says semantic locators are impossible in apps built from divs

context