In React Native Testing Library, what makes an element findable with getByRole, and how does the name option pick a login form's Sign in button?
answer
- must be an accessibility element
- Text, TextInput, Switch accessible by default
- plain View needs accessible={true}
- role or accessibilityRole both count
- name = label, else text content
basics
~20 sgetByRole matches only accessibility elements (Text, TextInput and Switch by default, or anything with accessible true, like the View a Pressable renders) whose role or accessibilityRole matches. The name option compares the accessible name: the label, else the text content.
solid answer
~40 sReact Native Testing Library's `*ByRole` runs several checks in order. The element must be an **accessibility element**: host `Text`, `TextInput` and `Switch` are by default, an `Image` with `alt` is, and any other host element only when its `accessible` prop is `true`. `Pressable` and `TouchableOpacity` render a host `View` with `accessible` already set, while a plain `View` with `role="button"` is skipped. Next, the `role` or `accessibilityRole` prop must match; a `Text` without one reports `text`. Then optional state and value filters apply, and finally `name`, which matches the **accessible name**: the label from `aria-label`, `accessibilityLabel` or a labelled-by reference if present, otherwise the text content of its children. So `getByRole('button', { name: 'Sign in' })` finds the pressable whose `Text` child reads Sign in.
code
tsx · 25 linesimport { Pressable, Text, TextInput, View } from 'react-native';
import { render, screen } from '@testing-library/react-native';
function LoginForm({ onSubmit }: { onSubmit: () => void }) {
return (
<View>
<TextInput accessibilityLabel="Email" />
<TextInput accessibilityLabel="Password" secureTextEntry />
<Pressable role="button" onPress={onSubmit}>
<Text>Sign in</Text>
</Pressable>
<View role="button">
<Text>Forgot password?</Text>
</View>
</View>
);
}
test('role queries need accessibility elements', async () => {
await render(<LoginForm onSubmit={jest.fn()} />);
expect(screen.getByRole('button', { name: 'Sign in' })).toBeOnTheScreen();
// The plain View is not accessible, so it is invisible to role queries:
expect(screen.queryByRole('button', { name: 'Forgot password?' })).not.toBeOnTheScreen();
});go deeper
Recall that getByRole takes a role and an optional name, and that name is usually the visible text of the button.
Explain the accessibility-element rule, why a plain View with a role is skipped while Pressable is found, and how label overrides text in the accessible name.
Use failing role queries to surface real accessibility defects, scope ambiguous matches with within, and keep state filters for assertions that need them.
Make role and name queries the team default so that tests double as an accessibility contract, and decide how the component library guarantees roles and labels.
## The checks behind a role query `screen.getByRole(role, options)` walks the host-element tree and keeps an element only if all of these hold, cheapest first: 1. **It is an accessibility element.** 2. **Its role matches.** The role comes from the `role` prop or, failing that, `accessibilityRole`. Without either, a host `Text` reports `text` and everything else reports `none`. `image` is normalized to `img`, so both spellings match. 3. **Its state matches** when `disabled`, `selected`, `checked`, `busy` or `expanded` is passed. 4. **Its accessibility value matches** when `value` (`min`, `max`, `now`, `text`) is passed. 5. **Its accessible name matches** when `name` is passed. ## What counts as an accessibility element | Host element | Default | How to make it one | |---|---|---| | `Text` | yes | — | | `TextInput` | yes | — | | `Switch` | yes | — | | `Image` | only with an `alt` prop | add `alt` | | `View` | no | `accessible={true}` | | `View` rendered by `Pressable` or `TouchableOpacity` | yes | set by the component | An explicit `accessible` prop always wins, so `accessible={false}` removes even a `Text` from role queries. This mirrors the platform: an element that is not an accessibility element is not a separate stop for the screen reader, so a test cannot "tap" it by role either. The classic surprise is a hand-made button: - `<View role="button"><Text>Sign in</Text></View>` is **not** found by `getByRole('button')`, because the `View` is not accessible; - `<Pressable role="button"><Text>Sign in</Text></Pressable>` **is** found, because `Pressable` renders its host `View` with `accessible` set. A failing role query is therefore often a real accessibility defect, not a test problem. ## How name is computed The **accessible name** is what a screen reader would announce: - if the element has a label (`aria-label` or `accessibilityLabel`, or text resolved through `aria-labelledby` or `accessibilityLabelledBy`, which point at a `nativeID`), that label is the name; - otherwise the name is built from the text content of its descendants. `name` accepts a string (exact after normalization) or a regular expression. For the login form: - `getByRole('button', { name: 'Sign in' })` finds the submit pressable; - `getByRole('alert', { name: /incorrect password/i })` finds an error `Text` given `role="alert"`; - `getByRole('switch', { name: 'Remember me' })` finds a labelled `Switch`. ## When several elements share a role and name If the screen shows two "Sign in" buttons, for example the form's and one inside a social-login sheet, `getByRole` throws a multiple-elements error. Scope the query instead of loosening it: - `within(screen.getByRole('dialog', { name: 'Other options' })).getByRole('button', { name: 'Sign in' })` queries only inside that container; - `within` returns the full query set bound to one element and is the tool for a single list row or a single screen in a navigation stack. ## Reading the failure message A miss reports the parameters it used, for example `Unable to find an element with role: button, name: Sign in`. Check, in order: is the element accessible, is the role on the host element rather than dropped by a wrapper, and does the label override the visible text you expected to match?
- Does getByRole('button', { name: 'Sign in' }) still match if the Pressable has accessibilityLabel='Log in to your account'?No. A label takes precedence over text content when computing the accessible name, so the name becomes 'Log in to your account'. The query must use that label, which is also what a screen reader announces.
- How do you query the Sign in button inside one list row when every row has one?Find the row first, for example by its role and name or text, then call `within(row).getByRole('button', { name: 'Sign in' })`. `within` returns all queries scoped to that element, so the ambiguity disappears without falling back to test IDs.
getByRole works like asking a screen-reader user to point at the Sign in button: they can only point at things the reader stops on, and they know each one by what it announces, its label if it has one, otherwise its text.
saying these in an interview costs you the question
- Any View with role='button' is found by getByRole('button').
- Only the accessibilityRole prop counts; the role prop is ignored.
- The name option matches the testID prop.
- Text content always wins over accessibilityLabel for the name.
- getByRole can return the Pressable composite component itself.