Skip to content

Synthetic touches (XCUITest/Detox) silently ignored on iPhone in portrait (horizontalSizeClass=compact), but work fine on iPad (regular) #1141

Description

@Lubron88

Synthetic touches (XCUITest/Detox) silently ignored on iPhone in portrait (horizontalSizeClass=compact), but work fine on iPad (regular)

Environment

  • react-native-pager-view: 8.0.0
  • detox: 20.50.4
  • React Native / Expo Dev Client
  • iOS 26.5, tested on multiple simulators: iPhone 14, iPhone 17, iPhone 17 Pro Max (all portrait), and iPad (landscape)
  • New Architecture (SwiftUI-based iOS implementation, confirmed via view hierarchy dump: react_native_pager_view.PagerViewProviderUIKitPagingViewPagingCollectionView, all wrapped through nested UIHostingView/SwiftUIIntrospect layers)

Summary

A TouchableOpacity rendered on the first page of a PagerView never receives onPressIn/onPressOut/onPress when tapped via a synthetic touch (XCUITest through Detox), but works perfectly with a real click on the same simulator. The failure is 100% reproducible on every iPhone model tested (regardless of screen size) while portrait, and never reproduces on iPad in landscape, using the exact same JS/JSX code path.

Reproduction

<PagerView testID="pager" initialPage={0}>
  <View key="1">
    <TouchableOpacity
      testID="my-button"
      onPress={() => console.log('onPress fired')}
      onPressIn={() => console.log('onPressIn fired')}
      onPressOut={() => console.log('onPressOut fired')}
    >
      <Text>Tap me</Text>
    </TouchableOpacity>
  </View>
  {/* ...other pages */}
</PagerView>

Detox test:

await element(by.id('my-button')).tap();

Expected behavior

onPressInonPressOutonPress fire in order, same as a real tap.

Actual behavior

  • On iPad, landscape (horizontalSizeClass == .regular): all three handlers fire correctly with .tap().
  • On any iPhone, portrait (horizontalSizeClass == .compact): none of the three handlers fire. No error is thrown by Detox — the tap action itself completes without exception, and Detox reports the element as hittable: true, visible: true, with a correct frame/activationPoint (confirmed via getAttributes()), yet nothing happens in the app.
  • A real mouse click on the same simulator, same button, same session, fires all three handlers correctly and immediately. This rules out any issue in the button's own code, layout, or onPress logic.

What we ruled out

  • Geometry / hit-testing target: getAttributes() before the tap shows a correct frame, elementFrame, activationPoint (centered within the visible colored box), hittable: true, visible: true, enabled: true — identical shape of data on both iPhone (fails) and iPad (passes).
  • Occlusion: full view hierarchy dump (--loglevel verbose) at the moment of failure shows no other view on top of the button; it is the last element in its local z-order.
  • Gesture variant: tried .tap(), .tap({x, y}) with explicit coordinates, .longPress(150), .longPress(600), .multiTap(2) — all fail identically (no handler fires).
  • Leaf vs. container element: moved the testID from the outer View to the inner Text (leaf node) — same failure.
  • Screen size: tested iPhone 14, iPhone 17, and iPhone 17 Pro Max (largest available) in portrait — all fail identically. This rules out screen size / safe area as the variable; the only difference between "works" and "fails" that we could isolate is horizontalSizeClass (compact for any iPhone in portrait, regular for iPad), not device model or physical screen size.
  • Simulator instance / degradation: reproduced identically on two freshly created simulator instances (simctl create from scratch), ruling out a corrupted/stale simulator as the cause.
  • Detox synchronization: adding device.disableSynchronization() around the tap made no difference.

Additional data point

Delaying the tap by ~1.5s after the screen is visible changes the failure mode (from a Detox-level "View is not hittable at its visible point" hit-test error, seemingly caused by hitting an animating full-screen UIView mid-transition, to a silent no-op with no handler firing at all) but does not fix the underlying issue.

Hypothesis (unconfirmed)

We suspect this may be related to how the SwiftUI-based paging container's own gesture recognizer (for horizontal swipe) claims/arbitrates touches on compact width, in a way that differs from regular width and that a synthetic (XCUITest-generated) touch loses in this arbitration while a real touch does not. We have not been able to confirm this at the native Swift level — this is a JS-side diagnosis only, using console.log in touch handlers and Detox's getAttributes()/view-hierarchy dump.

We found a related, unresolved Apple Developer Forums report describing SwiftUI Button actions being ignored inside a horizontal ScrollView near the top of a view (iOS/iPadOS 27 beta), which suggests this may be a broader category of SwiftUI gesture-arbitration issue rather than something specific to this library — but we have no confirmation either way.

Question for maintainers

  • Is this a known issue with the SwiftUI-based (New Architecture) iOS implementation introduced in 8.x?
  • Is there a recommended workaround (e.g. a prop to adjust gesture recognizer behavior, or a way to bypass the paging container's gesture claim for children) for consumers who need reliable XCUITest/Detox automation on iPhone in portrait?
  • Was this present in the pre-8.x (UIPageViewController-based) implementation? We have not tested a downgrade yet.

Happy to provide the full view hierarchy dumps, getAttributes() output, or a minimal reproduction repo if useful.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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