[Android] longPressAndDrag silently drags from (0,0) when normalized positions are NaN
Did you test using the latest Detox?
Yes — reproduced on 20.51.4, which is the latest published release at the time of writing. The relevant code is unchanged on master.
What happened?
On Android, passing NaN as the normalized position arguments to longPressAndDrag makes the gesture start and end at the absolute screen coordinate (0, 0) — a zero-distance touch in the screen's top-left corner. No error is raised: the action reports success, and the drag simply does nothing.
NaN is the documented "choose an optimal value automatically" idiom, and it is what the API's own doc example uses:
await element(by.id('draggable')).longPressAndDrag(2000, NaN, NaN, element(by.id('target')), NaN, NaN, 'fast', 0);The Android native action does not special-case NaN — LongPressAndDragAction.kt:
val startPoint = Point(
ceil(sourceViewPoint.x + sourceView.width * normalizedPositionX).toInt(),
ceil(sourceViewPoint.y + sourceView.height * normalizedPositionY).toInt()
)With normalizedPositionX = NaN: width * NaN = NaN → ceil(NaN) = NaN → and in Kotlin Double.NaN.toInt() is 0. Both endpoints collapse to (0, 0). There is no isNaN check anywhere in the file.
The action's own log line shows it clearly — note that Detox knows the correct view positions, yet computes (0, 0) for both points:
D LongPressAndDragAction: start:Point(0, 0), end:Point(0, 0) duration: 400, holdDuration: 0, \
scrollMotions: 50, source:[Point(55, 732),970x144], target:[Point(55, 566),970x145]
D LongPressAndDragAction: Performed swipe. Actual coordinates x=55, y=732. Normalized position x=NaN, y=NaNThe source row sits at (55, 732) with size 970x144 and the target at (55, 566) — so the intended drag was roughly (540, 804) → (540, 638). Instead the swipe is performed at (0, 0) → (0, 0).
There is a documentation angle too: detox.d.ts still marks the action as (iOS Only)…
/**
* Simulate long press on an element and then drag it to the position of the target element. (iOS Only)
* @example await element(by.id('draggable')).longPressAndDrag(2000, NaN, NaN, element(by.id('target')), NaN, NaN, 'fast', 0);
*/…while Android has shipped a native implementation since #4003 / #4423 (merged 2024-04-01). So the type declarations say the API is iOS-only, the docs site documents NaN without any platform caveat, and the Android implementation silently mishandles it.
What was the expected behaviour?
Either of these would be fine, and both are much better than the current behaviour:
- Treat
NaNas "use the element centre" on Android too, matching iOS and the documented meaning of the argument. - Fail loudly with a clear error if
NaNis not supported on Android — so the mistake surfaces at the call site.
What makes the current behaviour costly is the silence. The drag does nothing, the action reports success, and the test fails much later at an assertion that looks unrelated — in our case a reorder helper that read the list order and reported a missing item, which sent us looking at our app's gesture code and at the list rendering. Only a logcat of the native action revealed the real cause. Our own gesture logic was fine the whole time: a real adb shell input swipe between the same two rows reorders correctly on the first try.
Help us reproduce this issue!
- Render a vertically drag-reorderable list on Android (ours uses
react-native-gesture-handler'sGesture.Pan()), with each row carrying atestID. - Drag one row onto its predecessor using the doc's own
NaNidiom:await element(by.id('row-2')).longPressAndDrag(400, NaN, NaN, element(by.id('row-1')), NaN, NaN, 'slow', 0); - The action succeeds, but the list order is unchanged.
adb logcat | grep LongPressAndDragActionshowsstart:Point(0, 0), end:Point(0, 0). - Replacing the four
NaNs with0.5makes the drag work as expected.
Note for anyone hitting this: on Android holdDuration also cannot be omitted to get the documented default of 1000 — the JS bridge forwards it with no fallback, so an omitted argument throws holdDuration should be a number, but got undefined.
In what environment did this happen?
Detox version: 20.51.4
React Native version: 0.86.2
Has Fabric (React Native's new rendering system) enabled: yes (newArchEnabled=true)
Node version: v24.14.0
Device model: Pixel 7 emulator (AVD)
Android version: 15 (API 35)
Test-runner: jest
Device logs
Device logs (native action)D Detox : class com.wix.detox.espresso.DetoxAction, longPressAndDrag, [400, NaN, NaN, androidx.test.espresso.ViewInteraction@5448bab, NaN, NaN, false, 0]
I ViewInteraction: Performing 'longPressAndDrag' action on view (view.getTag() is "ordering-row-2" and view has effective visibility <VISIBLE>)
D LongPressAndDragAction: start:Point(0, 0), end:Point(0, 0) duration: 400, holdDuration: 0, scrollMotions: 50, source:[Point(55, 732),970x144], target:[Point(55, 566),970x145]
D LongPressAndDragAction: Performed swipe. Actual coordinates x=55, y=732. Normalized position x=NaN, y=NaNSource: wix/Detox