Skip to content

[Android] Support interacting with elements inside React Native Modal (Espresso inRoot) #4928

@yuki2006

Description

@yuki2006

Problem

Detox cannot interact with elements rendered inside React Native's <Modal> component on Android.

React Native's <Modal> creates a separate native Window (Dialog). Detox uses Espresso's onView(matcher).perform(action) without specifying a root matcher, so tap events are dispatched to the main Activity window and never reach the Modal's window.

This means:

  • element(by.id('button-inside-modal')).tap() silently does nothing (no error, but no effect)
  • waitFor(element(...)).toBeVisible() may find the element, but tap() fails to trigger onPress

Proposed Solution

Add Espresso's inRoot(isDialog()) support to Detox's Android view interaction layer.

In SingleViewActionPerformer.kt, the current code:

Espresso.onView(matcher).perform(action)

Could be extended to automatically detect and target dialog windows:

// Try default root first, fall back to dialog root
try {
    Espresso.onView(matcher).perform(action)
} catch (e: NoMatchingRootException) {
    Espresso.onView(matcher).inRoot(RootMatchers.isDialog()).perform(action)
}

Or expose an API for explicit root selection:

await element(by.id('modal-button')).inRoot('dialog').tap();

Alternatives Considered

  1. Replace <Modal> with <View style={absoluteFill}> — Works but requires modifying production code to accommodate testing limitations.

  2. UIAutomator (adb shell input tap)input tap coordinates also fail to reach Modal windows on some devices.

  3. System API (Epic [Epic] Extend system interaction support #4464) — The planned UIAutomator-based system.element() API would solve this for system dialogs, but React Native <Modal> is an app-level dialog, not a system dialog. It's unclear if the System API will cover this case.

Related Issues

Environment

  • Detox: 20.47+
  • React Native: 0.81
  • Android: API 31–34
  • Device: Physical device and emulator

Reproduction

// App component
<Modal visible={true} transparent>
  <Pressable testID="modal-button" onPress={() => console.log('pressed')}>
    <Text>Tap me</Text>
  </Pressable>
</Modal>

// Test
it('should tap button inside modal', async () => {
  await element(by.id('modal-button')).tap(); // ← silently fails
});

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions