byRole queries don't find element that don't have an explicitly set accessibilityRole prop
Describe the bug
react-native built-in elements are not queriable with byRole queries if you don't set the accessibilityRole prop.
Expected behaviour
byRole queries should be able to match some elements without accessibilityRole.
We however need to be conscious of what roles should be used to match what elements. My suggestion is checking each element with a voice-over and see what matches:
- should
<Pressable />bebuttonby default? <TextInput />will readInputon ios voiceover, but that is not anAccessibilityRoleas exported byreact-native.
We might need to think about types. The point raised in https://github.com/callstack/react-native-testing-library/pull/1127#discussion_r974160967 that suggests using TextMatch for the role will make it harder to discover those cases.
Steps to Reproduce
Unable to find an element with accessibilityRole: image
140 | const { getByRole } = render(<Image />);
141 |
> 142 | getByRole('image');
| ^
143 | });
144 | });
145 |
Versions
Latest.
This is a questions whether we want to emulate that behavior or should React Native actually set these roles by itself.
I wonder why React Native is not supporting more of ARIA roles? It even misses things like textbox or equivalent role, which seems a huge omission, that will somehow limit the usefulness of byRole + name query.
There is a valid point of if RN should support more roles. However, if you look at RTL, they do support by themselves both the raw dom element and custom role prop, with the getImplicitAriaRoles function. They also have similar behaviour to understanding the states that are not user set (checked for instance).
So at least, we should support AccessibilityRole supported by RN, that are handled at the native level and not through accessibilityRole.
Regarding getting implicit roles there is another relevant public API: getRoles() which we should take into account.
Looks like it's responsible for calculating both explicit and implicit roles for given element: https://github.com/testing-library/dom-testing-library/blob/a9a8cf26992ff0f6b4257b7300939f461d04440d/src/role-helpers.js#L155
I think we also need to think whether to support some ARIA roles that are important but not present in RN AccessibilityRole definition, especially: textbox, maybe something more.
Do we know which implicit roles we want to support for which components?
Initially we could start with:
textforTexttextboxforTextInput(not present inAccessibilityRole!)imageforImagebuttonforPressableand allTouchable*- not 100% sure about this, but seems reasonable
By default View would have none implicit role (or have none role - which probably be equivalent).
Implicit roles would be used by *ByRole queries only in case when there is no explicit role. Otherwise explicit role always overrrides implicit role.
I think we could take some inspiration from aria role, which is more exhaustive than AccessibilityRole (that I feel designed as something to set, rather than to query).
If we follow their logic:
Textby itself doesn't have a role,<p>doesn't have one on webTextInputis a hard one, web makes a difference between the different type of<input />, and it could be acombobox, buttextboxwould be a reasonable one to start with. How does atextareareads on web? Because they suggest using the different type of input, if a screen reader reads them differently, we could try using the same ones, and takingmultilineinto account- Yes I agree for
Image Pressable, it depends how it reads. Does the screen readers reads something if you don't specify a role? If thats' the case, yes, if not, I think it'd be safe to ignore it from an accessibility perspective, since it'd create a false sense of security for our users: they'dgetByRole('button')while the element wouldn't appear as a button to user.
I strongly believe that if an element is not read with a specific role by screen-readers, then we shouldn't determine an implicit role.
I think we need to build some playground repos for native, so that we have easier job when examining these roles. For native we could build a sample Expo app and the test it using VoiceOver on iOS and TalkBack on Android. If I understand correctly these screen readers should announce roles for the elements.
It also seems that there are difference between RN Accessiblity Roles, ARIA roles, UIKit Accessibility Traits and Android where I could not find anything in the API resembling roles.
E.g. ARIA does not have a role for static text, iOS has staticText trait, and RN has text.
We should follow recent RN activity https://github.com/facebook/react-native/pull/34538, and base most of our decisions on ARIA roles, but also taking into account how screen readers act on iOS/Android.
Add role. https://github.com/facebook/react-native/pull/34538#issuecomment-1232918518 Alias for accessibilityRole but with full list of ARIA roles allowed. Map role='slider' to accessibilityRole='adjustable' Map role='img' to accessibilityRole='image' Map role='presentation' to accessibilityRole='none' Map role='summary' to accessibilityRole='region'
This RN PR has mapping between ARIA and RN roles.
Not sure how reliable is ESLint A11y plugin, but there are some of interesting rules:
- has-accessibility-props: Enforce that <Touchable*> components only have either the accessibilityRole prop or both accessibilityTraits and accessibilityComponentType props set
- no-nested-touchables: Enforce if a view has accessible={true}, that there are no clickable elements inside
If we treat this as valid, we should assume that touchables (& pressable) do not have default role, neither button nor anything else. Also we should be only taking role into account if accessible is not false, as otherwise the element is no an accessiblity element (i.e. is presentation element). Not sure about the default value of accessible prop, probably is false for View as Pressable/TouchableOpacity render themselves as Views with accessible={true}.
Snapshot of:
<Pressable testID="test">
is
<View
accessible={true}
collapsable={false}
focusable={true}
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
onResponderGrant={[Function]}
onResponderMove={[Function]}
onResponderRelease={[Function]}
onResponderTerminate={[Function]}
onResponderTerminationRequest={[Function]}
onStartShouldSetResponder={[Function]}
testID="test"
/>
Default value of accessible for Text: seems to depend on plantform and props:
- https://github.com/facebook/react-native/blob/30411ae1a42e46d0e5a2da494a39ed2767ba8808/Libraries/Text/Text.js#L199
- https://github.com/facebook/react-native/blob/30411ae1a42e46d0e5a2da494a39ed2767ba8808/Libraries/Text/Text.js#L236
Default value of accessible for TextInput seems to be true:
- const accessible = props.accessible !== false;
BTW There seems to be some accessibility stuff that might help us with assumptions in RNTester app: https://github.com/facebook/react-native/blob/e8739e962de3398bc7e42675b1d87ab35993f705/packages/rn-tester/js/examples/Accessibility/AccessibilityExample.js
App itself is located here: https://github.com/facebook/react-native/blob/main/packages/rn-tester/README.md
In order to have more concrete information about iOS/Android behaviour, I've stared doing experiments how different a11y props combinations affects behaviour of assistive tech on iOS and Android. I share initial findings in newly created wiki page. If you have time, please contribute your research for other component types (TextInput, Image, Pressable/TouchableXx, View, etc) there.
I've checked for Pressable and Image on ios using VoiceOver:
- Pressable: it's not a button by default unfortunately, Voice just reads the text inside if not role is given
- Image: not accessible by default and not read as an image by default by VoiceOver. So the props accesisble and accessibilityRole are compulsory to make it accessible