A React Native Testing Library query fails although screen.debug() prints the element; which accessibility rules can hide it, and what does includeHiddenElements change?
answer
- queries skip hidden elements by default
- display none, aria-hidden, no-hide-descendants
- accessibilityElementsHidden on an ancestor
- a sibling with accessibilityViewIsModal
- opacity 0 does not hide
basics
~20 sRNTL queries skip elements hidden from accessibility by default: display none, aria-hidden, accessibilityElementsHidden, importantForAccessibility no-hide-descendants on the element or an ancestor, or a modal sibling. includeHiddenElements: true, or configure's defaultIncludeHiddenElements, makes queries match them anyway.
solid answer
~30 s`screen.debug()` prints the whole host tree, but React Native Testing Library queries filter out elements **hidden from accessibility**, because users could not perceive them. An element counts as hidden when it or any ancestor has `display: 'none'`, `aria-hidden` true, `accessibilityElementsHidden` true, or `importantForAccessibility="no-hide-descendants"`, or when it has a sibling host element with `aria-modal` or `accessibilityViewIsModal` true. `opacity: 0`, `accessible={false}` and `importantForAccessibility="no"` do not hide. Passing `{ includeHiddenElements: true }` (alias `hidden`) makes one query match hidden elements; `configure({ defaultIncludeHiddenElements: true })` changes the default. I use that per query to prove something is hidden, not globally to make tests pass.
code
tsx · 24 linesimport { Text, TextInput, View } from 'react-native';
import { isHiddenFromAccessibility, render, screen } from '@testing-library/react-native';
function LoginWithExpiredBanner() {
return (
<View>
<View>
<TextInput accessibilityLabel="Password" secureTextEntry />
</View>
<View accessibilityViewIsModal>
<Text>Session expired</Text>
</View>
</View>
);
}
test('the form behind a modal sibling is hidden', async () => {
await render(<LoginWithExpiredBanner />);
expect(screen.queryByLabelText('Password')).not.toBeOnTheScreen();
const field = screen.getByLabelText('Password', { includeHiddenElements: true });
expect(isHiddenFromAccessibility(field)).toBe(true);
});go deeper
Recall that RNTL queries skip elements users cannot perceive, such as display none, and that screen.debug() shows more than queries can find.
List the hiding conditions, including ancestors and modal siblings, the ones that do not hide, and the per-query and global switches.
Diagnose a failing query from debug output, decide whether the hiding is intended or a defect, and fix the test flow rather than widening queries.
Set a policy that keeps hidden-element filtering on across the suite, so component tests act as an early accessibility gate for the app.
## The symptom A login test calls `screen.getByLabelText('Password')` and gets `Unable to find an element with accessibility label: Password`, yet `screen.debug()` clearly prints a `TextInput` with that label. The two disagree because `debug()` prints every host element, while **queries exclude elements hidden from accessibility by default**. The reasoning is that a test should find only what a user could perceive. ## What counts as hidden RNTL walks from the element up through its ancestors. The element is hidden if any node on that path meets one of these conditions: | Condition | Where it comes from | |---|---| | `style` flattens to `display: 'none'` | layout | | `aria-hidden={true}` | cross-platform accessibility prop | | `accessibilityElementsHidden={true}` | iOS accessibility prop | | `importantForAccessibility="no-hide-descendants"` | Android accessibility prop | | a **sibling** host element has `aria-modal` or `accessibilityViewIsModal` true | iOS modal semantics | RNTL applies all of these in tests regardless of the platform the prop targets. Equally important is what does **not** hide an element: - `opacity: 0`, since it is not treated as inaccessible; - `accessible={false}`, `role="none"` or `accessibilityRole="none"`; - `importantForAccessibility="no"`; only `"no-hide-descendants"` counts as hiding. The helper `isHiddenFromAccessibility(element)`, also exported as `isInaccessible`, runs the same check and is handy when diagnosing. ## A typical login-screen case The app shows a "Session expired" panel over the form, rendered as a sibling `View` with `accessibilityViewIsModal`. While it is present, the form container is a sibling of a modal view, so every field inside it counts as hidden and `getByLabelText('Password')` fails. That is correct: a VoiceOver user cannot reach the form either. The fix is in the test flow: dismiss the panel first, then query the form. ## includeHiddenElements and its default - **Per query**: `screen.getByText('Session expired', { includeHiddenElements: true })` also matches hidden elements. `hidden: true` is an alias kept for compatibility with the web Testing Library. - **Globally**: `configure({ defaultIncludeHiddenElements: true })` changes the default for every query; `defaultHidden` is its alias. The shipped default is `false`. ## How to use it well 1. When a query unexpectedly fails, run `screen.debug()` and check the element's ancestors for the conditions in the table. 2. Decide whether the hiding is **intended**, such as a modal or a collapsed section, or a **bug**, such as a stale `aria-hidden` left on a container. 3. If intended, change the test flow so the element is visible before querying it. 4. Use `includeHiddenElements: true` in a single query when the assertion is precisely that something exists but is hidden, typically together with `isHiddenFromAccessibility`. 5. Do not flip the global default to silence failures: it makes queries find elements users cannot reach and hides real accessibility regressions. ## Why this design is valuable Hidden-element filtering makes component tests behave like an assistive-technology user. A form accidentally covered by an invisible modal sibling, a section left with `aria-hidden` after an animation, or a screen with `display: 'none'` still mounted all break the test, which is exactly the signal a team wants before release.
- Why does a Text with opacity: 0 still match getByText?RNTL does not treat `opacity: 0` as hidden from accessibility, because the element can still be reached by assistive technology. Only `display: 'none'`, the hiding accessibility props, and modal siblings exclude an element. To hide something from both users and queries, use one of those.
- When is it reasonable to set defaultIncludeHiddenElements to true globally?Almost never in an app suite. It can suit a library that renders into containers it does not control, but for app tests it makes queries find elements users cannot reach and masks accessibility regressions. Prefer the per-query option where a hidden element is the point of the assertion.
saying these in an interview costs you the question
- screen.debug() output lists only elements that queries can find.
- An element with opacity: 0 is excluded from RNTL queries.
- accessible={false} hides an element and its children from queries.
- Setting includeHiddenElements globally is the standard fix for failing queries.
- A modal sibling has no effect on queries in the test environment.