Detox: Tapping broken in latest version
Description
I’ve updated Detox and tapping returns Test Failed: View "_element_" is not hittable at point _coordinates_
. It doesn’t matter if the element is a View, TextView or if it’s hitting a TouchableOpacity or anything in particular, the tap always fails.
I can confirm that this doesn’t happen on Android
- I have tested this issue on the latest Detox release and it still reproduces
Environment (please complete the following information):
- Detox: 16.9.2 & 17.0.1
- React Native: 0.62.2
- Node: 14.0.0
- Device: iPhone 11
- Xcode: 11.5
- iOS: 13.5
- macOS:
Logs
https://gist.github.com/j320/67857ba0e3224fd48ece641b2151cbec
About this issue
- Original URL
- State: closed
- Created 4 years ago
- Reactions: 6
- Comments: 43 (29 by maintainers)
OK, I’ve made the discussed change:
https://github.com/wix/Detox/pull/2328
Interaction will now be as loose as possible, only a small 1x1 point visibility check at the interaction point. Let’s see how that works out.
You are probably right, from implementation point of view of the library. But from user point of view, where user is someone who uses detox, absolutely not. You clearly understand that the latest version of detox doesn’t work with latest react-navigation, because it doesn’t provide testID for all root elements. What is the point to create problems for users who is using detox? If user (someone using detox) wants to send a tap to text, just send it. Don’t explain hierarchy under it, that it is not a button, or it can’t be touched, under another view, etc. Don’t explain that is not a right way to do it because you think so. Think as user, who is going to use the testing framework.
UI is often complex, with overlapping/invisible views. And it is easier to hit object X, where X is Text, than 100X ways you described, including forcing everyone rewrite their frameworks. None of them works. Only stubs over detox. When I need to read a value from the object, I need the exact object. But when I need to touch… I don’t care if I hit button, text or icon on it. To iOS simulator, there’s no difference.
Just another example, which doesn’t work any more (worked in 17.4.4). I have full-screen view. I have drawer hidden behind right edge of the phone. How can I pull it out? It is invisible, and detox tells me it. in 17.4.4 I did mainView.swipe(left, fast, 0.999), and it worked - it swiped all screen from left to right, starting from the right edge. From 17.4.5 it swipes from the middle of the screen to the left edge. I don’t care how to do it properly, I just need swipe from {X1, Y1} to {X2, Y2}.
You think about implementation. Think bigger, from a user’s point of view, from a point of view of someone, who is using what you develop. Thank you! Detox is nice tool, but when it create such problems in 10h tests… And half of it stops working after next update.
Found the issue:
https://github.com/software-mansion/react-native-gesture-handler/blob/df5de67f96bae5fe5f0100dde320b1f0bc461b51/ios/RNGestureHandlerButton.m#L62
The button class captures touches that should go to the text view, which makes Detox decide the text view is not tappable. The only solution I see right now with the code as is, is for you to put a testID on the button and discover it in that way.
Thanks. Please keep the repo alive, I will look at it soon.
As I explained, Detox 17 uses a completely new interaction system, and it might not be fully stable. You can keep using Detox 16, or you can help us debug. What you describe about swiping sounds like a bug, and you should open an issue, and explain in detail what happens.
You do understand that our intention isn’t to put roadblocks for developers, yes? We try to encourage good practices. From early on, we made a decision not to support functionality that encourages bad practice. This approach has worked out, as you said, Detox is the most widely used tool for end-to-end.
The way the OS framework works, when you touch the screen, it asks window by window, view by view “is this point inside you and do you want it?”. When you
tap(point)
(tapAtPoint() is deprecated but available), I run the same hit test functionality that iOS does, only to see if the view you want is the one taking the touch. If some other view turns out to get the touch (in your caseRNGestureHandlerButton
), often there is no way to know if it’s part of a<ButtonRoot><View/><View/></ButtonRoot>
or a<ParentWithGestureHandler><Something><Something><Something><DisabledButton /></Something></Something></Something></ParentWithGestureHandler>
. In your case, you are targeting a text label (that to the OS rejects touches) that could be part of a button, but could also be part of some container that might erroneously catch a touch. So I don’t see why asking you to be more exact is so bad. When you specify that your view has a descendant, you are narrowing down the search and also providing the exact target with which you’d like to interact. This API is there for a reason. Now, if your “fundamental” framework was properly written, you could have just given the button an identifier and be done with it, but since you cannot, I don’t see what else would satisfy you other than a wild west, and I’m not really convinced it’s something I’d like to offer.Internally, we’ve had push back for strictness, of course, but we already caught abuses of the previous model, where people were doing things that the user would never do, thus having invalid tests. And then, with Detox 17, that approach broke, and at first it was “but it worked until now, why no more?”, and as we investigated, it turned out the tests were odd.
If we lived in the native world, instead of this RN crapshoot, we’d have a much more ordered hierarchy, and things would be much more easier to generalize. This is basically what XCUITest does. It only exposes some of the accessibility aspects of views. But with React Native being the mess that it is, button is not really a button (from a native standpoint), Detox has to shoot for lowest common denominator, which is very low with RN. This is the limitation of this technology.
Thinking about it, even with web stuff like “react-native-testing-library”, how can it know if the static text you attempted to tap is hittable or not? It’s the same problem, unless RN somehow annotates
RNGestureHandlerButton
as a hittable that swallows its children’s touches. I am not familiar with any such mechanism in JS.Regarding tap and obscured view; Detox, by default with
tap()
, attempts to tap the accessibility activation point; it’s the same that Voice Over activates when a blind user double taps the screen. If you want Detox to behave as you expect, you should fix your app to provide a proper activation point for that button. Not only will it fix your tests, but make your app accessible. By default, the accessibility activation point is the middle of the view.https://developer.apple.com/documentation/objectivec/nsobject/1615179-accessibilityactivationpoint
I’m aware of tapAtPoint. I use it for example, to tap ‘header-back’ when normal tap() on certain reason doesn’t work:
element(by.id('header-back')).atIndex(i).tapAtPoint({x: 10, y: 15})
It doesn’t work with tap(), because I suppose something sits over the point it touches.
For my previous example, when tapping Text object on another Button, why should I touch by coordinates, not the Text object? Doesn’t it look contr-intuitive to normal programmer expectation? Access by object, not by {x, y}.
For the case
<ButtonRoot><View/><View/></ButtonRoot>
, I do expect both Views could be touched separately, even if I have an access to ButtonRoot (which I don’t have with Drawer.Navigator and won’t have soon or ever).I don’t know what the difference is, so I can say for sure. Can you please create a small demo project reproducing it? I will look at the native views, and will be able to answer for sure.
This doesn’t help me. I need to see the iOS view hierarchy, not RN’s interpretation of it.
Follow this: https://github.com/wix/Detox/blob/master/docs/Guide.DebuggingInXcode.md
In Xcode, add an exception breakpoint. Reproduce your failing test, and it should hit the break point when the above assertion fails (“View is not hittable at point”). Then click on the view hierarchy button in Xcode, look for both the text view and the button that should be above it. If you find them one on top of the other, there is no bug. If you see different results, post a screenshot here.