detox 20.14.10-smoke.1 → 20.15.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (75) hide show
  1. package/.eslintrc.js +0 -4
  2. package/Detox-android/com/wix/detox/{20.14.10-smoke.1/detox-20.14.10-smoke.1-javadoc.jar → 20.15.0/detox-20.15.0-javadoc.jar} +0 -0
  3. package/Detox-android/com/wix/detox/20.15.0/detox-20.15.0-javadoc.jar.md5 +1 -0
  4. package/Detox-android/com/wix/detox/20.15.0/detox-20.15.0-javadoc.jar.sha1 +1 -0
  5. package/Detox-android/com/wix/detox/20.15.0/detox-20.15.0-javadoc.jar.sha256 +1 -0
  6. package/Detox-android/com/wix/detox/20.15.0/detox-20.15.0-javadoc.jar.sha512 +1 -0
  7. package/Detox-android/com/wix/detox/{20.14.10-smoke.1/detox-20.14.10-smoke.1-sources.jar → 20.15.0/detox-20.15.0-sources.jar} +0 -0
  8. package/Detox-android/com/wix/detox/20.15.0/detox-20.15.0-sources.jar.md5 +1 -0
  9. package/Detox-android/com/wix/detox/20.15.0/detox-20.15.0-sources.jar.sha1 +1 -0
  10. package/Detox-android/com/wix/detox/20.15.0/detox-20.15.0-sources.jar.sha256 +1 -0
  11. package/Detox-android/com/wix/detox/20.15.0/detox-20.15.0-sources.jar.sha512 +1 -0
  12. package/Detox-android/com/wix/detox/20.15.0/detox-20.15.0.aar +0 -0
  13. package/Detox-android/com/wix/detox/20.15.0/detox-20.15.0.aar.md5 +1 -0
  14. package/Detox-android/com/wix/detox/20.15.0/detox-20.15.0.aar.sha1 +1 -0
  15. package/Detox-android/com/wix/detox/20.15.0/detox-20.15.0.aar.sha256 +1 -0
  16. package/Detox-android/com/wix/detox/20.15.0/detox-20.15.0.aar.sha512 +1 -0
  17. package/Detox-android/com/wix/detox/{20.14.10-smoke.1/detox-20.14.10-smoke.1.pom → 20.15.0/detox-20.15.0.pom} +1 -1
  18. package/Detox-android/com/wix/detox/20.15.0/detox-20.15.0.pom.md5 +1 -0
  19. package/Detox-android/com/wix/detox/20.15.0/detox-20.15.0.pom.sha1 +1 -0
  20. package/Detox-android/com/wix/detox/20.15.0/detox-20.15.0.pom.sha256 +1 -0
  21. package/Detox-android/com/wix/detox/20.15.0/detox-20.15.0.pom.sha512 +1 -0
  22. package/Detox-android/com/wix/detox/maven-metadata.xml +4 -4
  23. package/Detox-android/com/wix/detox/maven-metadata.xml.md5 +1 -1
  24. package/Detox-android/com/wix/detox/maven-metadata.xml.sha1 +1 -1
  25. package/Detox-android/com/wix/detox/maven-metadata.xml.sha256 +1 -1
  26. package/Detox-android/com/wix/detox/maven-metadata.xml.sha512 +1 -1
  27. package/Detox-ios-src.tbz +0 -0
  28. package/Detox-ios.tbz +0 -0
  29. package/android/detox/src/full/java/com/wix/detox/espresso/DetoxAction.java +11 -6
  30. package/android/detox/src/main/java/com/wix/detox/espresso/DeviceDisplay.kt +1 -1
  31. package/android/detox/src/main/java/com/wix/detox/espresso/scroll/ScrollHelper.java +113 -16
  32. package/android/detox/src/testFull/java/com/wix/detox/espresso/scroll/ScrollHelperTest.kt +188 -0
  33. package/detox.d.ts +5 -2
  34. package/package.json +2 -2
  35. package/src/android/actions/native.js +2 -2
  36. package/src/android/core/NativeElement.js +3 -4
  37. package/src/android/espressoapi/DetoxAction.js +9 -1
  38. package/src/android/interactions/native.js +2 -2
  39. package/src/android/matchers/native.js +1 -1
  40. package/src/artifacts/providers/index.js +1 -1
  41. package/src/artifacts/screenshot/SimulatorScreenshotPlugin.js +0 -1
  42. package/src/artifacts/templates/artifact/Artifact.js +1 -1
  43. package/src/client/AsyncWebSocket.js +2 -2
  44. package/src/client/Client.js +2 -2
  45. package/src/devices/allocation/drivers/android/attached/AttachedAndroidAllocDriver.js +0 -1
  46. package/src/devices/allocation/drivers/android/emulator/EmulatorAllocDriver.js +0 -1
  47. package/src/devices/allocation/drivers/android/emulator/EmulatorVersionResolver.js +1 -1
  48. package/src/devices/allocation/drivers/ios/SimulatorAllocDriver.js +0 -1
  49. package/src/devices/allocation/factories/android.js +1 -1
  50. package/src/devices/allocation/factories/ios.js +1 -1
  51. package/src/devices/common/drivers/android/exec/ADB.js +1 -2
  52. package/src/devices/common/drivers/android/tools/AppInstallHelper.js +0 -2
  53. package/src/devices/runtime/drivers/android/emulator/EmulatorDriver.js +0 -1
  54. package/src/devices/runtime/factories/ios.js +0 -2
  55. package/src/ios/expectTwo.js +6 -3
  56. package/src/ipc/IPCServer.js +1 -1
  57. package/src/realms/DetoxPrimaryContext.js +0 -3
  58. package/src/utils/invocationTraceDescriptions.js +3 -2
  59. package/Detox-android/com/wix/detox/20.14.10-smoke.1/detox-20.14.10-smoke.1-javadoc.jar.md5 +0 -1
  60. package/Detox-android/com/wix/detox/20.14.10-smoke.1/detox-20.14.10-smoke.1-javadoc.jar.sha1 +0 -1
  61. package/Detox-android/com/wix/detox/20.14.10-smoke.1/detox-20.14.10-smoke.1-javadoc.jar.sha256 +0 -1
  62. package/Detox-android/com/wix/detox/20.14.10-smoke.1/detox-20.14.10-smoke.1-javadoc.jar.sha512 +0 -1
  63. package/Detox-android/com/wix/detox/20.14.10-smoke.1/detox-20.14.10-smoke.1-sources.jar.md5 +0 -1
  64. package/Detox-android/com/wix/detox/20.14.10-smoke.1/detox-20.14.10-smoke.1-sources.jar.sha1 +0 -1
  65. package/Detox-android/com/wix/detox/20.14.10-smoke.1/detox-20.14.10-smoke.1-sources.jar.sha256 +0 -1
  66. package/Detox-android/com/wix/detox/20.14.10-smoke.1/detox-20.14.10-smoke.1-sources.jar.sha512 +0 -1
  67. package/Detox-android/com/wix/detox/20.14.10-smoke.1/detox-20.14.10-smoke.1.aar +0 -0
  68. package/Detox-android/com/wix/detox/20.14.10-smoke.1/detox-20.14.10-smoke.1.aar.md5 +0 -1
  69. package/Detox-android/com/wix/detox/20.14.10-smoke.1/detox-20.14.10-smoke.1.aar.sha1 +0 -1
  70. package/Detox-android/com/wix/detox/20.14.10-smoke.1/detox-20.14.10-smoke.1.aar.sha256 +0 -1
  71. package/Detox-android/com/wix/detox/20.14.10-smoke.1/detox-20.14.10-smoke.1.aar.sha512 +0 -1
  72. package/Detox-android/com/wix/detox/20.14.10-smoke.1/detox-20.14.10-smoke.1.pom.md5 +0 -1
  73. package/Detox-android/com/wix/detox/20.14.10-smoke.1/detox-20.14.10-smoke.1.pom.sha1 +0 -1
  74. package/Detox-android/com/wix/detox/20.14.10-smoke.1/detox-20.14.10-smoke.1.pom.sha256 +0 -1
  75. package/Detox-android/com/wix/detox/20.14.10-smoke.1/detox-20.14.10-smoke.1.pom.sha512 +0 -1
package/.eslintrc.js CHANGED
@@ -16,10 +16,6 @@ module.exports = {
16
16
  env: {
17
17
  node: true
18
18
  },
19
- globals: {
20
- // TODO: remove use of fail() across the project because Jest Circus doesn't support it
21
- 'fail': true
22
- },
23
19
  rules: {
24
20
  '@typescript-eslint/no-unused-vars': [
25
21
  'warn',
@@ -0,0 +1 @@
1
+ 6af6a1128b61c579b4a5c82d2865f585
@@ -0,0 +1 @@
1
+ 4dba1aa3f352698d234cccc7f69d8d3e69350458
@@ -0,0 +1 @@
1
+ 0d73e6570afca6e11cefdb63125dd01bdd0f33143089402c752c506b5acf85a3
@@ -0,0 +1 @@
1
+ cda5ab07eac2a8371056647e4b6391a1c42e2d4a5b1a6a7a810a398ed4f29946441ea9da132a236cee7e523b09b6b6ea8f1510e5faaec0061193c84767e1d849
@@ -0,0 +1 @@
1
+ b912691ddb7318d42de9431f7435bc9a
@@ -0,0 +1 @@
1
+ 5c90afac3b3a7d5fa2ba83e650604f6f9bc00682
@@ -0,0 +1 @@
1
+ 5eabd613ae0284bc97755f7e29cf0d233e281ae7635afeeb0875437401c3ddb8
@@ -0,0 +1 @@
1
+ 5ef628faf38474b46638efc54f932b15824f5735497279f0153fe9c6afc2f7de9a151ee46e51724ce93ff22515bf43b86147f8330c67a49b34dd45ce3b129c5d
@@ -0,0 +1 @@
1
+ 8a77aaff1489b4887d6873aaae1357a3
@@ -0,0 +1 @@
1
+ d5f06bdfb31d314d08ef800ab5d9148502702d2a
@@ -0,0 +1 @@
1
+ 7f45586ecc61fe0f158c8701000529d566299aa4b24f9539e4cc4ea2d29d898e
@@ -0,0 +1 @@
1
+ b00a7385379509967eb9ffed2c619e19008f03fc2d5534fb80bd08c31a6f9d937d9269858a4f3d03878d4979259d43da7ccec3173954454ff50c9a18590f1d20
@@ -3,7 +3,7 @@
3
3
  <modelVersion>4.0.0</modelVersion>
4
4
  <groupId>com.wix</groupId>
5
5
  <artifactId>detox</artifactId>
6
- <version>20.14.10-smoke.1</version>
6
+ <version>20.15.0</version>
7
7
  <packaging>aar</packaging>
8
8
  <name>Detox</name>
9
9
  <description>Gray box end-to-end testing and automation library for mobile apps</description>
@@ -0,0 +1 @@
1
+ b214c6ec949dbb966c2ab104243efbb5
@@ -0,0 +1 @@
1
+ 91c5ac02c59420f8a6eaca4af5fa5a5d40901b92
@@ -0,0 +1 @@
1
+ 8e39181294f58719e0d42ea4b6220526012c229b47ab6db988acd904a8bba64c
@@ -0,0 +1 @@
1
+ c919a336c80ca89a3d41975a6b8574539f93234105f7762870afdd1540ef44763b00ecf7860b6cb22457ff9c714047f5c78f9a341c9ad923fc8beb25fd1d1331
@@ -3,11 +3,11 @@
3
3
  <groupId>com.wix</groupId>
4
4
  <artifactId>detox</artifactId>
5
5
  <versioning>
6
- <latest>20.14.10-smoke.1</latest>
7
- <release>20.14.10-smoke.1</release>
6
+ <latest>20.15.0</latest>
7
+ <release>20.15.0</release>
8
8
  <versions>
9
- <version>20.14.10-smoke.1</version>
9
+ <version>20.15.0</version>
10
10
  </versions>
11
- <lastUpdated>20240104110851</lastUpdated>
11
+ <lastUpdated>20240111111712</lastUpdated>
12
12
  </versioning>
13
13
  </metadata>
@@ -1 +1 @@
1
- 0c2cc338d28fe459893c6426d40bfb84
1
+ 9e22bc0ea12d6e5775b68bde874bdf22
@@ -1 +1 @@
1
- 5b29e1df2b25d012ef873e9fa3ba52fc62e77ab3
1
+ add0ba9ace5604f9bad42acfe204f32728323ce4
@@ -1 +1 @@
1
- 5a134d3a6373d5709703819bf4654715f2ac0aac1b3750098e55d0c3743e34e4
1
+ 8d6d198043d40230347ae63f85625557fd6b2f45494d80c14e19e7e8e2f19fd2
@@ -1 +1 @@
1
- 928db132d12d47b07944fcc1f58dd85b0fbb8227ede6935265502e4927eb2cbcc4cd22c08cb77e92c5549ecf6b644b0afb93a63759968777feb3685f867bcaa5
1
+ 7411bfbd2cbeb1e65e91618e520b083f54253f8b5e8f17f8b187de2716d3bf809c45246eb2bca80a9011f439f65f08053442daa846c4833bcb28f378a42e18b6
package/Detox-ios-src.tbz CHANGED
Binary file
package/Detox-ios.tbz CHANGED
Binary file
@@ -79,9 +79,14 @@ public class DetoxAction {
79
79
  * Scrolls to the edge of the given scrollable view.
80
80
  *
81
81
  * @param edge Direction to scroll (see {@link MotionDir})
82
+ * @param startOffsetPercentX Percentage denoting where the scroll should start from on the X-axis, with respect to the scrollable view.
83
+ * @param startOffsetPercentY Percentage denoting where the scroll should start from on the Y-axis, with respect to the scrollable view.
82
84
  * @return ViewAction
83
85
  */
84
- public static ViewAction scrollToEdge(final int edge) {
86
+ public static ViewAction scrollToEdge(final int edge, double startOffsetPercentX, double startOffsetPercentY) {
87
+ final Float _startOffsetPercentX = startOffsetPercentX < 0 ? null : (float) startOffsetPercentX;
88
+ final Float _startOffsetPercentY = startOffsetPercentY < 0 ? null : (float) startOffsetPercentY;
89
+
85
90
  return actionWithAssertions(new ViewAction() {
86
91
  @Override
87
92
  public Matcher<View> getConstraints() {
@@ -97,7 +102,7 @@ public class DetoxAction {
97
102
  public void perform(UiController uiController, View view) {
98
103
  try {
99
104
  for (int i = 0; i < 100; i++) {
100
- ScrollHelper.performOnce(uiController, view, edge);
105
+ ScrollHelper.performOnce(uiController, view, edge, _startOffsetPercentX, _startOffsetPercentY);
101
106
  }
102
107
  throw new DetoxRuntimeException("Scrolling a lot without reaching the edge: force-breaking the loop");
103
108
  } catch (ScrollEdgeException e) {
@@ -112,8 +117,8 @@ public class DetoxAction {
112
117
  *
113
118
  * @param direction Direction to scroll (see {@link MotionDir})
114
119
  * @param amountInDP Density Independent Pixels
115
- * @param startOffsetPercentX Percentage denoting where X-swipe should start, with respect to the scrollable view.
116
- * @param startOffsetPercentY Percentage denoting where Y-swipe should start, with respect to the scrollable view.
120
+ * @param startOffsetPercentX Percentage denoting where the scroll should start from on the X-axis, with respect to the scrollable view.
121
+ * @param startOffsetPercentY Percentage denoting where the scroll should start from on the Y-axis, with respect to the scrollable view.
117
122
  */
118
123
  public static ViewAction scrollInDirection(final int direction, final double amountInDP, double startOffsetPercentX, double startOffsetPercentY) {
119
124
  final Float _startOffsetPercentX = startOffsetPercentX < 0 ? null : (float) startOffsetPercentX;
@@ -129,8 +134,8 @@ public class DetoxAction {
129
134
  *
130
135
  * @param direction Direction to scroll (see {@link MotionDir})
131
136
  * @param amountInDP Density Independent Pixels
132
- * @param startOffsetPercentX Percentage denoting where X-swipe should start, with respect to the scrollable view.
133
- * @param startOffsetPercentY Percentage denoting where Y-swipe should start, with respect to the scrollable view.
137
+ * @param startOffsetPercentX Percentage denoting where the scroll should start from on the X-axis, with respect to the scrollable view.
138
+ * @param startOffsetPercentY Percentage denoting where the scroll should start from on the Y-axis, with respect to the scrollable view.
134
139
  */
135
140
  public static ViewAction scrollInDirectionStaleAtEdge(final int direction, final double amountInDP, double startOffsetPercentX, double startOffsetPercentY) {
136
141
  final Float _startOffsetPercentX = startOffsetPercentX < 0 ? null : (float) startOffsetPercentX;
@@ -17,7 +17,7 @@ object DeviceDisplay {
17
17
  }
18
18
 
19
19
  @JvmStatic
20
- fun getScreenSizeInPX(): FloatArray? {
20
+ fun getScreenSizeInPX(): FloatArray {
21
21
  val metrics = getDisplayMetrics()
22
22
  return floatArrayOf(metrics.widthPixels.toFloat(), metrics.heightPixels.toFloat())
23
23
  }
@@ -1,14 +1,18 @@
1
1
  package com.wix.detox.espresso.scroll;
2
2
 
3
3
  import android.content.Context;
4
+ import android.graphics.Insets;
4
5
  import android.graphics.Point;
6
+ import android.os.Build;
5
7
  import android.util.Log;
6
8
  import android.view.View;
7
9
  import android.view.ViewConfiguration;
10
+ import android.view.WindowInsets;
8
11
 
9
12
  import com.wix.detox.action.common.MotionDir;
10
13
  import com.wix.detox.espresso.DeviceDisplay;
11
14
 
15
+ import androidx.annotation.VisibleForTesting;
12
16
  import androidx.test.espresso.UiController;
13
17
  import androidx.test.platform.app.InstrumentationRegistry;
14
18
 
@@ -42,8 +46,8 @@ public class ScrollHelper {
42
46
  *
43
47
  * @param direction Direction to scroll (see {@link MotionDir})
44
48
  * @param amountInDP Density Independent Pixels
45
- * @param startOffsetPercentX Percentage denoting where X-swipe should start, with respect to the scrollable view. Null means select automatically.
46
- * @param startOffsetPercentY Percentage denoting where Y-swipe should start, with respect to the scrollable view. Null means select automatically.
49
+ * @param startOffsetPercentX Percentage denoting where the scroll should start from on the X-axis, with respect to the scrollable view. Null means select automatically.
50
+ * @param startOffsetPercentY Percentage denoting where the scroll should start from on the Y-axis, with respect to the scrollable view. Null means select automatically.
47
51
  */
48
52
  public static void perform(UiController uiController, View view, @MotionDir int direction, double amountInDP, Float startOffsetPercentX, Float startOffsetPercentY) throws ScrollEdgeException {
49
53
  final int amountInPx = DeviceDisplay.convertDpiToPx(amountInDP);
@@ -51,7 +55,7 @@ public class ScrollHelper {
51
55
  final int times = amountInPx / safeScrollableRangePx;
52
56
  final int remainder = amountInPx % safeScrollableRangePx;
53
57
 
54
- Log.d(LOG_TAG, "prescroll amountDP="+amountInDP + " amountPx="+amountInPx + " scrollableRangePx="+safeScrollableRangePx + " times="+times + " remainder="+remainder);
58
+ Log.d(LOG_TAG, "prescroll amountDP=" + amountInDP + " amountPx=" + amountInPx + " scrollableRangePx=" + safeScrollableRangePx + " times=" + times + " remainder=" + remainder);
55
59
 
56
60
  for (int i = 0; i < times; ++i) {
57
61
  scrollOnce(uiController, view, direction, safeScrollableRangePx, startOffsetPercentX, startOffsetPercentY);
@@ -64,10 +68,12 @@ public class ScrollHelper {
64
68
  * of the screen.)
65
69
  *
66
70
  * @param direction Direction to scroll (see {@link @MotionDir})
71
+ * @param startOffsetPercentX Percentage denoting where the scroll should start from on the X-axis, with respect to the scrollable view. Null means select automatically.
72
+ * @param startOffsetPercentY Percentage denoting where the scroll should start from on the Y-axis, with respect to the scrollable view. Null means select automatically.
67
73
  */
68
- public static void performOnce(UiController uiController, View view, @MotionDir int direction) throws ScrollEdgeException {
74
+ public static void performOnce(UiController uiController, View view, @MotionDir int direction, Float startOffsetPercentX, Float startOffsetPercentY) throws ScrollEdgeException {
69
75
  final int scrollableRangePx = getViewSafeScrollableRangePix(view, direction);
70
- scrollOnce(uiController, view, direction, scrollableRangePx, null, null);
76
+ scrollOnce(uiController, view, direction, scrollableRangePx, startOffsetPercentX, startOffsetPercentY);
71
77
  }
72
78
 
73
79
  private static void scrollOnce(UiController uiController, View view, @MotionDir int direction, int userAmountPx, Float startOffsetPercentX, Float startOffsetPercentY) throws ScrollEdgeException {
@@ -113,25 +119,32 @@ public class ScrollHelper {
113
119
  }
114
120
  }
115
121
 
116
- private static int getViewSafeScrollableRangePix(View view, @MotionDir int direction) {
122
+ @VisibleForTesting
123
+ public static int getViewSafeScrollableRangePix(View view, @MotionDir int direction) {
117
124
  final float[] screenSize = DeviceDisplay.getScreenSizeInPX();
118
125
  final int[] pos = new int[2];
119
126
  view.getLocationInWindow(pos);
120
127
 
121
128
  int range;
122
129
  switch (direction) {
123
- case MOTION_DIR_LEFT: range = (int) ((screenSize[0] - pos[0]) * SCROLL_RANGE_SAFE_PERCENT); break;
124
- case MOTION_DIR_RIGHT: range = (int) ((pos[0] + view.getWidth()) * SCROLL_RANGE_SAFE_PERCENT); break;
125
- case MOTION_DIR_UP: range = (int) ((screenSize[1] - pos[1]) * SCROLL_RANGE_SAFE_PERCENT); break;
126
- default: range = (int) ((pos[1] + view.getHeight()) * SCROLL_RANGE_SAFE_PERCENT); break;
130
+ case MOTION_DIR_LEFT:
131
+ range = (int) ((screenSize[0] - pos[0]) * SCROLL_RANGE_SAFE_PERCENT);
132
+ break;
133
+ case MOTION_DIR_RIGHT:
134
+ range = (int) ((pos[0] + view.getWidth()) * SCROLL_RANGE_SAFE_PERCENT);
135
+ break;
136
+ case MOTION_DIR_UP:
137
+ range = (int) ((screenSize[1] - pos[1]) * SCROLL_RANGE_SAFE_PERCENT);
138
+ break;
139
+ default:
140
+ range = (int) ((pos[1] + view.getHeight()) * SCROLL_RANGE_SAFE_PERCENT);
141
+ break;
127
142
  }
128
143
  return range;
129
144
  }
130
145
 
131
- private static Point getScrollStartPoint(View view, @MotionDir int direction, Float startOffsetPercentX, Float startOffsetPercentY) {
146
+ private static int[] getScrollStartOffsetInView(View view, @MotionDir int direction, Float startOffsetPercentX, Float startOffsetPercentY) {
132
147
  final int safetyOffset = DeviceDisplay.convertDpiToPx(1);
133
-
134
- Point point = getGlobalViewLocation(view);
135
148
  float offsetFactorX;
136
149
  float offsetFactorY;
137
150
  int safetyOffsetX;
@@ -169,8 +182,87 @@ public class ScrollHelper {
169
182
  int offsetX = ((int) (view.getWidth() * offsetFactorX) + safetyOffsetX);
170
183
  int offsetY = ((int) (view.getHeight() * offsetFactorY) + safetyOffsetY);
171
184
 
172
- point.offset(offsetX, offsetY);
173
- return point;
185
+ return new int[]{offsetX, offsetY};
186
+ }
187
+
188
+ /**
189
+ * Calculates the scroll start point, with respect to the global screen coordinates and gesture insets.
190
+ * @param view The view to scroll.
191
+ * @param direction The scroll direction.
192
+ * @param startOffsetPercentX The scroll start offset, as a percentage of the view's width. Null means select automatically.
193
+ * @param startOffsetPercentY The scroll start offset, as a percentage of the view's height. Null means select automatically.
194
+ * @return a Point object, denoting the scroll start point.
195
+ */
196
+ private static Point getScrollStartPoint(View view, @MotionDir int direction, Float startOffsetPercentX, Float startOffsetPercentY) {
197
+ Point result = getGlobalViewLocation(view);
198
+
199
+ // 1. Calculate the scroll start point, with respect to the view's location.
200
+ int[] coordinates = getScrollStartOffsetInView(view, direction, startOffsetPercentX, startOffsetPercentY);
201
+
202
+ // 2. Make sure that the start point is within the scrollable area, taking into account the system gesture insets.
203
+ coordinates = applyScreenInsets(view, direction, coordinates[0], coordinates[1]);
204
+
205
+ result.offset(coordinates[0], coordinates[1]);
206
+ return result;
207
+ }
208
+
209
+ /**
210
+ * Calculates the scroll start point, with respect to the system gesture insets.
211
+ * @param view
212
+ * @param direction The scroll direction.
213
+ * @param x The scroll start point, with respect to the view's location.
214
+ * @param y The scroll start point, with respect to the view's location.
215
+ * @return an array of two integers, denoting the scroll start point, with respect to the system gesture insets.
216
+ */
217
+ private static int[] applyScreenInsets(View view, int direction, int x, int y) {
218
+ // System gesture insets are only available on Android Q (29) and above.
219
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
220
+ return new int[]{x, y};
221
+ }
222
+
223
+ final float[] displaySize = DeviceDisplay.getScreenSizeInPX();
224
+
225
+ // Calculate the min/max scrollable area, taking into account the system gesture insets.
226
+ // By default we assume the scrollable area is the entire screen.
227
+ // 2dp is a safety offset to make sure we don't hit the system gesture area.
228
+ int gestureSafeOffset = DeviceDisplay.convertDpiToPx(2);
229
+ int minX = gestureSafeOffset;
230
+ int minY = gestureSafeOffset;
231
+ float maxX = displaySize[0] - gestureSafeOffset;
232
+ float maxY = displaySize[1] - gestureSafeOffset;
233
+
234
+ // Try to get the root window insets, and if available, use them to calculate the scrollable area.
235
+ WindowInsets rootWindowInsets = view.getRootWindowInsets();
236
+ if (rootWindowInsets == null) {
237
+ Log.w(LOG_TAG, "Could not get root window insets");
238
+ } else {
239
+ Insets gestureInsets = rootWindowInsets.getSystemGestureInsets();
240
+ minX = gestureInsets.left;
241
+ minY = gestureInsets.top;
242
+ maxX -= gestureInsets.right;
243
+ maxY -= gestureInsets.bottom;
244
+
245
+ Log.d(LOG_TAG,
246
+ "System gesture insets: " +
247
+ gestureInsets + " minX=" + minX + " minY=" + minY + " maxX=" + maxX + " maxY=" + maxY + " currentX=" + x + " currentY=" + y);
248
+ }
249
+
250
+ switch (direction) {
251
+ case MOTION_DIR_UP:
252
+ y = (int) Math.max(y, minY);
253
+ break;
254
+ case MOTION_DIR_DOWN:
255
+ y = (int) Math.min(y, maxY);
256
+ break;
257
+ case MOTION_DIR_LEFT:
258
+ x = (int) Math.max(x, minX);
259
+ break;
260
+ case MOTION_DIR_RIGHT:
261
+ x = (int) Math.min(x, maxX);
262
+ break;
263
+ }
264
+
265
+ return new int[]{x, y};
174
266
  }
175
267
 
176
268
  private static Point getScrollEndPoint(Point startPoint, @MotionDir int direction, int userAmountPx, Float startOffsetPercentX, Float startOffsetPercentY) {
@@ -209,13 +301,18 @@ public class ScrollHelper {
209
301
  return point;
210
302
  }
211
303
 
304
+ /**
305
+ * Calculates the global location of the view on the screen.
306
+ * @param view The view to calculate.
307
+ * @return a Point object, denoting the global location of the view.
308
+ */
212
309
  private static Point getGlobalViewLocation(View view) {
213
310
  int[] pos = new int[2];
214
311
  view.getLocationInWindow(pos);
215
312
  return new Point(pos[0], pos[1]);
216
313
  }
217
314
 
218
- private static ViewConfiguration getViewConfiguration() {
315
+ public static ViewConfiguration getViewConfiguration() {
219
316
  if (viewConfiguration == null) {
220
317
  final Context applicationContext = InstrumentationRegistry.getInstrumentation().getTargetContext().getApplicationContext();
221
318
  viewConfiguration = ViewConfiguration.get(applicationContext);
@@ -0,0 +1,188 @@
1
+ package com.wix.detox.espresso.scroll
2
+
3
+ import android.graphics.Insets
4
+ import android.view.MotionEvent
5
+ import android.view.View
6
+ import android.view.WindowInsets
7
+ import androidx.test.espresso.UiController
8
+ import androidx.test.platform.app.InstrumentationRegistry
9
+ import com.wix.detox.action.common.MOTION_DIR_DOWN
10
+ import com.wix.detox.action.common.MOTION_DIR_LEFT
11
+ import com.wix.detox.action.common.MOTION_DIR_RIGHT
12
+ import com.wix.detox.action.common.MOTION_DIR_UP
13
+ import com.wix.detox.espresso.DeviceDisplay
14
+ import org.junit.Test
15
+ import org.junit.runner.RunWith
16
+ import org.mockito.kotlin.any
17
+ import org.mockito.kotlin.argumentCaptor
18
+ import org.mockito.kotlin.mock
19
+ import org.mockito.kotlin.verify
20
+ import org.mockito.kotlin.whenever
21
+ import org.robolectric.RobolectricTestRunner
22
+ import org.robolectric.annotation.Config
23
+ import kotlin.test.assertEquals
24
+
25
+ private const val INSETS_SIZE = 100
26
+ private const val SCROLL_RANGE_SAFE_PERCENT = 0.9f // ScrollHelper.SCROLL_RANGE_SAFE_PERCENT
27
+
28
+ @Config(
29
+ qualifiers = "xxxhdpi", // 1280x1880
30
+ sdk = [33]
31
+ )
32
+ @RunWith(RobolectricTestRunner::class)
33
+ class ScrollHelperTest {
34
+
35
+ private val display = DeviceDisplay.getScreenSizeInPX()
36
+ private val displayWidth = display[0].toInt()
37
+ private val displayHeight = display[1].toInt()
38
+ private val touchSlopPx = ScrollHelper.getViewConfiguration().scaledTouchSlop
39
+ private val safetyMarginPx = DeviceDisplay.convertDpiToPx(2.0)
40
+
41
+ private val uiControllerMock = mock<UiController>()
42
+ private val viewMock = mockViewWithGestureNavigation(displayWidth, displayHeight)
43
+
44
+ @Test
45
+ fun `should scrolling down by 200 when gesture navigation enabled`() {
46
+ val amountInDp = 200.0
47
+ val amountInPx = amountInDp * DeviceDisplay.getDensity()
48
+
49
+ ScrollHelper.perform(uiControllerMock, viewMock, MOTION_DIR_DOWN, amountInDp, null, null)
50
+
51
+ val upEvent = getUpEvent()
52
+ // Verify that the scroll started at the center of the view
53
+ assertEquals(displayWidth / 2.0, upEvent.x.toDouble(), 0.0)
54
+ // Verify that the scroll ended at the center of the view minus the requested amount
55
+ assertEquals(displayHeight - amountInPx - touchSlopPx - safetyMarginPx - INSETS_SIZE, upEvent.y.toDouble(), 0.0)
56
+ }
57
+
58
+ @Test
59
+ fun `should scrolling down by 200 when gesture navigation disabled`() {
60
+ val amountInDp = 200.0
61
+ val amountInPx = amountInDp * DeviceDisplay.getDensity()
62
+
63
+ val viewMock = mockViewWithoutGestureNavigation(displayWidth, displayHeight)
64
+ ScrollHelper.perform(uiControllerMock, viewMock, MOTION_DIR_DOWN, amountInDp, null, null)
65
+
66
+ val upEvent = getUpEvent()
67
+ // Verify that the scroll started at the center of the view
68
+ assertEquals(displayWidth / 2.0, upEvent.x.toDouble(), 0.0)
69
+ // Verify that the scroll ended at the center of the view minus the requested amount
70
+ assertEquals(displayHeight - amountInPx - touchSlopPx - safetyMarginPx, upEvent.y.toDouble(), 0.0)
71
+ }
72
+
73
+ @Test
74
+ fun `should scroll down to edge on full screen view when gesture navigation enabled`() {
75
+ ScrollHelper.performOnce(uiControllerMock, viewMock, MOTION_DIR_DOWN, null, null)
76
+ val upEvent = getUpEvent()
77
+ val amountInPx = displayHeight * SCROLL_RANGE_SAFE_PERCENT
78
+
79
+ // Calculate where the scroll should end
80
+ val targetY = displayHeight - amountInPx -
81
+ touchSlopPx -
82
+ safetyMarginPx -
83
+ INSETS_SIZE
84
+
85
+ assertEquals(displayWidth / 2.0, upEvent.x.toDouble(), 0.0)
86
+ assertEquals(targetY, upEvent.y, 0.0f)
87
+ }
88
+
89
+ @Test
90
+ fun `should scroll left to edge on full screen view when gesture navigation enabled`() {
91
+ ScrollHelper.performOnce(uiControllerMock, viewMock, MOTION_DIR_LEFT, null, null)
92
+ val upEvent = getUpEvent()
93
+ val amountInPx = displayWidth * SCROLL_RANGE_SAFE_PERCENT
94
+
95
+ // Calculate where the scroll should end
96
+ val targetX = amountInPx +
97
+ touchSlopPx +
98
+ INSETS_SIZE
99
+
100
+ assertEquals(targetX, upEvent.x, 0.0f)
101
+ assertEquals(displayHeight / 2.0, upEvent.y.toDouble(), 0.0)
102
+ }
103
+
104
+ @Test
105
+ fun `should scroll up to edge on full screen view when gesture navigation enabled`() {
106
+ ScrollHelper.performOnce(uiControllerMock, viewMock, MOTION_DIR_UP,null, null)
107
+ val upEvent = getUpEvent()
108
+ val amountInPx = displayHeight * SCROLL_RANGE_SAFE_PERCENT
109
+
110
+ // Calculate where the scroll should end
111
+ val targetY = amountInPx +
112
+ touchSlopPx +
113
+ INSETS_SIZE
114
+
115
+ assertEquals(displayWidth / 2.0, upEvent.x.toDouble(), 0.0)
116
+ assertEquals(targetY, upEvent.y, 0.0f)
117
+ }
118
+
119
+ @Test
120
+ fun `should scroll right to edge on full screen view when gesture navigation enabled`() {
121
+ ScrollHelper.performOnce(uiControllerMock, viewMock, MOTION_DIR_RIGHT, null, null)
122
+ val upEvent = getUpEvent()
123
+ val amountInPx = displayWidth * SCROLL_RANGE_SAFE_PERCENT
124
+
125
+ // Calculate where the scroll should end
126
+ val targetX = displayWidth - amountInPx -
127
+ touchSlopPx -
128
+ safetyMarginPx -
129
+ INSETS_SIZE
130
+
131
+ assertEquals(targetX, upEvent.x, 0.0f)
132
+ assertEquals(displayHeight / 2.0, upEvent.y.toDouble(), 0.0)
133
+ }
134
+
135
+ /**
136
+ * Get the performed UP event from the ui controller
137
+ */
138
+ private fun getUpEvent(): MotionEvent {
139
+ val capture = argumentCaptor<Iterable<MotionEvent>>()
140
+ // Capture the events from the ui controller
141
+ verify(uiControllerMock).injectMotionEventSequence(capture.capture())
142
+
143
+ val listOfCapturedEvents = capture.firstValue.toList()
144
+ // The last event is the UP event with the target coordinates. All of the rest are not interesting
145
+ return listOfCapturedEvents.last()
146
+ }
147
+
148
+ private fun mockViewWithoutGestureNavigation(displayWidth: Int, displayHeight: Int): View {
149
+ // This is how we disable gesture navigation
150
+ val windowInsets = mock<WindowInsets>() {
151
+ whenever(it.systemGestureInsets).thenReturn(
152
+ Insets.of(0, 0, 0, 0)
153
+ )
154
+ }
155
+
156
+ return mockView(displayWidth, displayHeight, windowInsets)
157
+ }
158
+
159
+ /**
160
+ * Mock a view with gesture navigation enabled
161
+ */
162
+ private fun mockViewWithGestureNavigation(displayWidth: Int, displayHeight: Int): View {
163
+ // This is how we enable gesture navigation
164
+ val windowInsets = mock<WindowInsets>() {
165
+ whenever(it.systemGestureInsets).thenReturn(
166
+ Insets.of(INSETS_SIZE, INSETS_SIZE, INSETS_SIZE, INSETS_SIZE)
167
+ )
168
+ }
169
+
170
+ return mockView(displayWidth, displayHeight, windowInsets)
171
+ }
172
+
173
+ private fun mockView(
174
+ displayWidth: Int,
175
+ displayHeight: Int,
176
+ windowInsets: WindowInsets
177
+ ): View {
178
+ val view = mock<View>() {
179
+ whenever(it.width).thenReturn(displayWidth)
180
+ whenever(it.height).thenReturn(displayHeight)
181
+ whenever(it.canScrollVertically(any())).thenReturn(true) // We allow endless scroll
182
+ whenever(it.canScrollHorizontally(any())).thenReturn(true) // We allow endless scroll
183
+ whenever(it.context).thenReturn(InstrumentationRegistry.getInstrumentation().targetContext)
184
+ whenever(it.rootWindowInsets).thenReturn(windowInsets)
185
+ }
186
+ return view
187
+ }
188
+ }
package/detox.d.ts CHANGED
@@ -1363,10 +1363,13 @@ declare global {
1363
1363
 
1364
1364
  /**
1365
1365
  * Scroll to edge.
1366
- * @example await element(by.id('scrollView')).scrollTo('bottom');
1366
+ * @param edge - left|right|top|bottom
1367
+ * @param startPositionX - the X starting scroll position, in percentage; valid input: `[0.0, 1.0]`, `NaN`; default: `NaN`—choose the best value automatically
1368
+ * @param startPositionY - the Y starting scroll position, in percentage; valid input: `[0.0, 1.0]`, `NaN`; default: `NaN`—choose the best value automatically
1369
+ * @example await element(by.id('scrollView')).scrollTo('bottom', NaN, 0.85);
1367
1370
  * @example await element(by.id('scrollView')).scrollTo('top');
1368
1371
  */
1369
- scrollTo(edge: Direction): Promise<void>;
1372
+ scrollTo(edge: Direction, startPositionX?: number, startPositionY?: number): Promise<void>;
1370
1373
 
1371
1374
  /**
1372
1375
  * Adjust slider to position.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "detox",
3
3
  "description": "E2E tests and automation for mobile",
4
- "version": "20.14.10-smoke.1",
4
+ "version": "20.15.0",
5
5
  "bin": {
6
6
  "detox": "local-cli/cli.js"
7
7
  },
@@ -109,5 +109,5 @@
109
109
  "browserslist": [
110
110
  "node 14"
111
111
  ],
112
- "gitHead": "6aff705fc1fa17683b29c7d84e35b53f1e8c50e4"
112
+ "gitHead": "bed7b965b2180bd3c628e242b9f78a80660c371f"
113
113
  }
@@ -81,10 +81,10 @@ class ScrollAmountStopAtEdgeAction extends Action {
81
81
  }
82
82
 
83
83
  class ScrollEdgeAction extends Action {
84
- constructor(edge) {
84
+ constructor(edge, startPositionX = -1, startPositionY = -1) {
85
85
  super();
86
86
 
87
- this._call = invoke.callDirectly(DetoxActionApi.scrollToEdge(edge));
87
+ this._call = invoke.callDirectly(DetoxActionApi.scrollToEdge(edge, startPositionX, startPositionY));
88
88
  }
89
89
  }
90
90
 
@@ -93,12 +93,12 @@ class NativeElement {
93
93
  return await new ActionInteraction(this._invocationManager, this._matcher, action, traceDescription).execute();
94
94
  }
95
95
 
96
- async scrollTo(edge) {
96
+ async scrollTo(edge, startPositionX, startPositionY) {
97
97
  // override the user's element selection with an extended matcher that looks for UIScrollView children
98
98
  this._matcher = this._matcher._extendToDescendantScrollViews();
99
99
 
100
- const action = new actions.ScrollEdgeAction(edge);
101
- const traceDescription = actionDescription.scrollTo(edge);
100
+ const action = new actions.ScrollEdgeAction(edge, startPositionX, startPositionY);
101
+ const traceDescription = actionDescription.scrollTo(edge, startPositionX, startPositionY);
102
102
  return await new ActionInteraction(this._invocationManager, this._matcher, action, traceDescription).execute();
103
103
  }
104
104
 
@@ -140,7 +140,6 @@ class NativeElement {
140
140
  }
141
141
 
142
142
  async takeScreenshot(screenshotName) {
143
- // TODO this should be moved to a lower-layer handler of this use-case
144
143
  const action = new actions.TakeElementScreenshot();
145
144
  const traceDescription = actionDescription.takeScreenshot(screenshotName);
146
145
  const resultBase64 = await new ActionInteraction(this._invocationManager, this._matcher, action, traceDescription).execute();
@@ -68,8 +68,10 @@ class DetoxAction {
68
68
  };
69
69
  }
70
70
 
71
- static scrollToEdge(edge) {
71
+ static scrollToEdge(edge, startOffsetPercentX, startOffsetPercentY) {
72
72
  if (typeof edge !== "string") throw new Error("edge should be a string, but got " + (edge + (" (" + (typeof edge + ")"))));
73
+ if (typeof startOffsetPercentX !== "number") throw new Error("startOffsetPercentX should be a number, but got " + (startOffsetPercentX + (" (" + (typeof startOffsetPercentX + ")"))));
74
+ if (typeof startOffsetPercentY !== "number") throw new Error("startOffsetPercentY should be a number, but got " + (startOffsetPercentY + (" (" + (typeof startOffsetPercentY + ")"))));
73
75
  return {
74
76
  target: {
75
77
  type: "Class",
@@ -79,6 +81,12 @@ class DetoxAction {
79
81
  args: [{
80
82
  type: "Integer",
81
83
  value: sanitize_android_edge(edge)
84
+ }, {
85
+ type: "Double",
86
+ value: startOffsetPercentX
87
+ }, {
88
+ type: "Double",
89
+ value: startOffsetPercentY
82
90
  }]
83
91
  };
84
92
  }
@@ -28,7 +28,7 @@ class ActionInteraction extends Interaction {
28
28
  constructor(invocationManager, matcher, action, traceDescription) {
29
29
  super(invocationManager, traceDescription);
30
30
  this._call = EspressoDetoxApi.perform(matcher, action._call);
31
- // TODO: move this.execute() here from the caller
31
+ // TODO [2024-12-01]: move this.execute() here from the caller
32
32
  }
33
33
  }
34
34
 
@@ -39,7 +39,7 @@ class MatcherAssertionInteraction extends Interaction {
39
39
 
40
40
  matcher = notCondition ? matcher.not : matcher;
41
41
  this._call = DetoxAssertionApi.assertMatcher(call(element._call), matcher._call.value);
42
- // TODO: move this.execute() here from the caller
42
+ // TODO [2024-12-01]: move this.execute() here from the caller
43
43
  }
44
44
  }
45
45
 
@@ -76,7 +76,7 @@ class ToggleMatcher extends NativeMatcher {
76
76
  }
77
77
  }
78
78
 
79
- // TODO: Please be aware, that this is just a dummy matcher
79
+ // NOTE: Please be aware, that this is just a dummy matcher
80
80
  class TraitsMatcher extends NativeMatcher {
81
81
  constructor(value) {
82
82
  super();
@@ -1,5 +1,5 @@
1
1
  class ArtifactPluginsProvider {
2
- declareArtifactPlugins({ client }) {} // eslint-disable-line no-unused-vars
2
+ declareArtifactPlugins({ client }) {} // eslint-disable-line no-unused-vars,@typescript-eslint/no-unused-vars
3
3
  }
4
4
 
5
5
  class AndroidArtifactPluginsProvider extends ArtifactPluginsProvider {
@@ -1,7 +1,6 @@
1
1
  const path = require('path');
2
2
 
3
3
  const fs = require('../../utils/fsext');
4
- const log = require('../../utils/logger').child({ cat: 'artifacts-plugin,artifacts' });
5
4
  const FileArtifact = require('../templates/artifact/FileArtifact');
6
5
  const temporaryPath = require('../utils/temporaryPath');
7
6
 
@@ -107,7 +107,7 @@ class Artifact {
107
107
 
108
108
  async doStop() {}
109
109
 
110
- async doSave(artifactPath) {} // eslint-disable-line no-unused-vars
110
+ async doSave(_artifactPath) {}
111
111
 
112
112
  async doDiscard() {}
113
113
  }
@@ -134,7 +134,7 @@ class AsyncWebSocket {
134
134
  }
135
135
  }
136
136
 
137
- // TODO: handle this leaked abstraction some day
137
+ // TODO [2024-12-01]: handle this leaked abstraction some day
138
138
  hasPendingActions() {
139
139
  return _.some(this.inFlightPromises, p => p.message.type !== 'currentStatus');
140
140
  }
@@ -168,7 +168,7 @@ class AsyncWebSocket {
168
168
  case WebSocket.CONNECTING: return 'opening';
169
169
  case WebSocket.OPEN: return 'open';
170
170
  /* istanbul ignore next */
171
- default: // TODO: [2021-12-01] throw new DetoxInternalError('...'); instead
171
+ default:
172
172
  return undefined;
173
173
  }
174
174
  }
@@ -187,14 +187,14 @@ class Client {
187
187
  this._whenAppIsReady = new Deferred();
188
188
 
189
189
  await this._whenAppIsConnected.promise;
190
- // TODO: optimize traffic (!) - we can just listen for 'ready' event
190
+ // TODO [2024-12-01]: optimize traffic (!) - we can just listen for 'ready' event
191
191
  // if app always sends it upon load completion. On iOS it works,
192
192
  // but not on Android. Afterwards, this will suffice:
193
193
  //
194
194
  // await this._whenAppIsReady.promise;
195
195
  }
196
196
 
197
- // TODO: move to else branch after the optimization
197
+ // TODO [2024-12-01]: move to else branch after the optimization ↑↑
198
198
  if (!this._whenAppIsReady.isResolved()) {
199
199
  this._whenAppIsReady = new Deferred();
200
200
  await this.sendAction(new actions.Ready());
@@ -41,7 +41,6 @@ class AttachedAndroidAllocDriver {
41
41
  async postAllocate(deviceCookie) {
42
42
  const { adbName } = deviceCookie;
43
43
 
44
- // TODO Also disable native animations?
45
44
  await this._adb.apiLevel(adbName);
46
45
  await this._adb.unlockScreen(adbName);
47
46
  }
@@ -5,7 +5,6 @@
5
5
 
6
6
  const _ = require('lodash');
7
7
 
8
- const Deferred = require('../../../../../utils/Deferred');
9
8
  const log = require('../../../../../utils/logger').child({ cat: 'device,device-allocation' });
10
9
 
11
10
  const { patchAvdSkinConfig } = require('./patchAvdSkinConfig');
@@ -7,7 +7,7 @@ class EmulatorVersionResolver {
7
7
  this.version = undefined;
8
8
  }
9
9
 
10
- async resolve(isHeadless = false) { // TODO Make isHeadless a config arg (i.e. through c'tor)?
10
+ async resolve(isHeadless = false) {
11
11
  if (!this.version) {
12
12
  this.version = await this._resolve(isHeadless);
13
13
  }
@@ -39,7 +39,6 @@ class SimulatorAllocDriver {
39
39
  async allocate(deviceConfig) {
40
40
  const deviceQuery = new SimulatorQuery(deviceConfig.device);
41
41
 
42
- // TODO Delegate this onto a well tested allocator class
43
42
  const udid = await this._deviceRegistry.registerDevice(async () => {
44
43
  return await this._findOrCreateDevice(deviceQuery);
45
44
  });
@@ -42,7 +42,7 @@ class AndroidEmulator extends DeviceAllocatorFactory {
42
42
  }
43
43
 
44
44
  class AndroidAttached extends DeviceAllocatorFactory {
45
- _createDriver({ detoxSession, detoxConfig }) {
45
+ _createDriver({ detoxSession }) {
46
46
  const serviceLocator = require('../../servicelocator/android');
47
47
  const adb = serviceLocator.adb;
48
48
  const DeviceRegistry = require('../../allocation/DeviceRegistry');
@@ -2,7 +2,7 @@
2
2
  const DeviceAllocatorFactory = require('./base');
3
3
 
4
4
  class IosSimulator extends DeviceAllocatorFactory {
5
- _createDriver({ detoxConfig, detoxSession, eventEmitter }) {
5
+ _createDriver({ detoxConfig, detoxSession }) {
6
6
  const AppleSimUtils = require('../../../devices/common/drivers/ios/tools/AppleSimUtils');
7
7
  const applesimutils = new AppleSimUtils();
8
8
 
@@ -8,7 +8,7 @@ const { escape } = require('../../../../../utils/pipeCommands');
8
8
  const DeviceHandle = require('../tools/DeviceHandle');
9
9
  const EmulatorHandle = require('../tools/EmulatorHandle');
10
10
 
11
- const INSTALL_TIMEOUT = 45000; // TODO Double check 45s makes sense
11
+ const INSTALL_TIMEOUT = 45000;
12
12
 
13
13
  class ADB {
14
14
  constructor() {
@@ -345,7 +345,6 @@ class ADB {
345
345
  return this.adbCmd(deviceId, `emu kill`);
346
346
  }
347
347
 
348
- // TODO refactor the whole thing so as to make usage of BinaryExec -- similar to EmulatorExec
349
348
  async adbCmd(deviceId, params, options = {}) {
350
349
  const serial = `${deviceId ? `-s ${deviceId}` : ''}`;
351
350
  const cmd = `"${this.adbBin}" ${serial} ${params}`;
@@ -1,5 +1,3 @@
1
- // TODO Tweak such that if apk's already exist on the device (need to store uniquely), they will not be resent (would optimize cloud, for example)
2
-
3
1
  class AppInstallHelper {
4
2
  constructor(adb, fileTransfer) {
5
3
  this._adb = adb;
@@ -11,7 +11,6 @@ const AndroidDriver = require('../AndroidDriver');
11
11
  * @property forceAdbInstall { Boolean }
12
12
  */
13
13
 
14
- // TODO Unit test coverage
15
14
  class EmulatorDriver extends AndroidDriver {
16
15
  /**
17
16
  * @param deps { EmulatorDriverDeps }
@@ -2,8 +2,6 @@ const RuntimeDeviceFactory = require('./base');
2
2
 
3
3
  class RuntimeDriverFactoryIos extends RuntimeDeviceFactory {
4
4
  _createDriverDependencies(commonDeps) {
5
- const { eventEmitter } = commonDeps;
6
-
7
5
  const AppleSimUtils = require('../../../devices/common/drivers/ios/tools/AppleSimUtils');
8
6
  const applesimutils = new AppleSimUtils();
9
7
 
@@ -247,10 +247,13 @@ class Element {
247
247
  return this.withAction('scroll', traceDescription, pixels, direction, startPositionX, startPositionY);
248
248
  }
249
249
 
250
- scrollTo(edge) {
250
+ scrollTo(edge, startPositionX = NaN, startPositionY = NaN) {
251
251
  if (!['left', 'right', 'top', 'bottom'].some(option => option === edge)) throw new Error('edge should be one of [left, right, top, bottom], but got ' + edge);
252
- const traceDescription = actionDescription.scrollTo(edge);
253
- return this.withAction('scrollTo', traceDescription, edge);
252
+ if (typeof startPositionX !== 'number') throw new Error('startPositionX should be a number, but got ' + (startPositionX + (' (' + (typeof startPositionX + ')'))));
253
+ if (typeof startPositionY !== 'number') throw new Error('startPositionY should be a number, but got ' + (startPositionY + (' (' + (typeof startPositionY + ')'))));
254
+
255
+ const traceDescription = actionDescription.scrollTo(edge, startPositionX, startPositionY);
256
+ return this.withAction('scrollTo', traceDescription, edge, startPositionX, startPositionY);
254
257
  }
255
258
 
256
259
  swipe(direction, speed = 'fast', normalizedSwipeOffset = NaN, normalizedStartingPointX = NaN, normalizedStartingPointY = NaN) {
@@ -40,7 +40,7 @@ class IPCServer {
40
40
  this._ipc.config.logger = (msg) => this._logger.trace(msg);
41
41
 
42
42
  await new Promise((resolve) => {
43
- // TODO: handle reject
43
+ // It is worth to handle rejection here some day
44
44
  this._ipc.serve(() => resolve());
45
45
  this._ipc.server.on('conductEarlyTeardown', this.onConductEarlyTeardown.bind(this));
46
46
  this._ipc.server.on('registerContext', this.onRegisterContext.bind(this));
@@ -98,7 +98,6 @@ class DetoxPrimaryContext extends DetoxContext {
98
98
  data: this[$sessionState],
99
99
  }, getCurrentCommand());
100
100
 
101
- // TODO: IPC Server creation ought to be delegated to a generator/factory.
102
101
  const IPCServer = require('../ipc/IPCServer');
103
102
  this[_ipcServer] = new IPCServer({
104
103
  sessionState: this[$sessionState],
@@ -111,7 +110,6 @@ class DetoxPrimaryContext extends DetoxContext {
111
110
 
112
111
  await this[_ipcServer].init();
113
112
 
114
- // TODO: Detox-server creation ought to be delegated to a generator/factory.
115
113
  const DetoxServer = require('../server/DetoxServer');
116
114
  if (sessionConfig.autoStart) {
117
115
  this[_wss] = new DetoxServer({
@@ -124,7 +122,6 @@ class DetoxPrimaryContext extends DetoxContext {
124
122
  await this[_wss].open();
125
123
  }
126
124
 
127
- // TODO: double check that this config is indeed propogated onto the client create at the detox-worker side
128
125
  if (!sessionConfig.server && this[_wss]) {
129
126
  // @ts-ignore
130
127
  sessionConfig.server = `ws://localhost:${this[_wss].port}`;
@@ -12,8 +12,9 @@ module.exports = {
12
12
  pinchWithAngle: (direction, speed, angle) => `pinch with direction ${direction}, speed ${speed}, and angle ${angle}`,
13
13
  replaceText: (value) => `replace input text: "${value}"`,
14
14
  scroll: (amount, direction, startPositionX, startPositionY) =>
15
- `scroll ${amount} pixels ${direction}${startPositionX !== undefined && startPositionY !== undefined ? ` from normalized position (${startPositionX}, ${startPositionY})` : ''}`,
16
- scrollTo: (edge) => `scroll to ${edge}`,
15
+ `scroll ${amount} pixels ${direction}${startPositionX !== undefined || startPositionY !== undefined ? ` from normalized position (${startPositionX}, ${startPositionY})` : ''}`,
16
+ scrollTo: (edge, startPositionX, startPositionY) =>
17
+ `scroll to ${edge} ${startPositionX !== undefined || startPositionY !== undefined ? ` from normalized position (${startPositionX}, ${startPositionY})` : ''}`,
17
18
  scrollToIndex: (index) => `scroll to index #${index}`,
18
19
  setColumnToValue: (column, value) => `set column ${column} to value ${value}`,
19
20
  setDatePickerDate: (dateString, dateFormat) => `set date picker date to ${dateString} using format ${dateFormat}`,
@@ -1 +0,0 @@
1
- eaf5c6ab60c7263a63ded415775ca4bb
@@ -1 +0,0 @@
1
- d9e21af9ba2d52a1de261f6d67ce0164ad6abe74
@@ -1 +0,0 @@
1
- 691dfc26633bf96a4b4e85192596bcdc55dce2422dc5be9ea411e72d4b4d21ac
@@ -1 +0,0 @@
1
- 8bada7530f9c27c654d88e42c4d516dc3171096db17fb72e74a3495ee65ed47ab6657b3ad20047d45c8a735cc8fe094fdb2c381a9e28f60eca9da1d6b4ca3f91
@@ -1 +0,0 @@
1
- 3eafa3c68aa4052456fdb2949577c4b9
@@ -1 +0,0 @@
1
- cda1ec11d74883ff6b9200812a9274c0dd6ad9c6
@@ -1 +0,0 @@
1
- f8b7caf61b6e1ffc3233b570be4d12b76b334d15bfce9b7dd102fe4ed2567af6
@@ -1 +0,0 @@
1
- 9df51128bb51f571479522a86589c5aee21f8cc4b1cc652fcfc17712e406632b83e65a5c59e2229fd3ec3709e8c0c9baef0b44ed3aabdfaa85dd3cc848104780
@@ -1 +0,0 @@
1
- c27920150917efb5b25878186e904fe0
@@ -1 +0,0 @@
1
- ad9e12738ca3c82606a58bacea6d50b51687aee9
@@ -1 +0,0 @@
1
- ab07c0011f9cee612e5b56ea42618978b75a5cfff9841766fc3f796e730c3571
@@ -1 +0,0 @@
1
- a26f964a1ef775262740a23fbbeca9e5754a5da7896bcab22a476f684d40168b67ad4a97087a1f6fe484343e70eb32063adfeac3a2879f887e8d7fc616e6eacc
@@ -1 +0,0 @@
1
- cff533719289526fac23e59c4e616616
@@ -1 +0,0 @@
1
- d017c7b801f2b09f6790aa2a401f8eaaac49b383
@@ -1 +0,0 @@
1
- e1ccabafa0a01ced62ce084048746fef6be5355f716c1213f7d7794089afb958
@@ -1 +0,0 @@
1
- f4c3c6a0d61bde3d6613ad3c01b759b39ca96b6746f6df703dc5c4cc958870ffe3d8154c98dca9cb0a639025ec4c0d5f6af6c134498f846ed4847f57ec9c1714