detox 20.27.7-smoke.0 → 20.31.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (115) hide show
  1. package/Detox-android/com/wix/detox/{20.27.7-smoke.0/detox-20.27.7-smoke.0-sources.jar → 20.30.0/detox-20.30.0-sources.jar} +0 -0
  2. package/Detox-android/com/wix/detox/20.30.0/detox-20.30.0-sources.jar.md5 +1 -0
  3. package/Detox-android/com/wix/detox/20.30.0/detox-20.30.0-sources.jar.sha1 +1 -0
  4. package/Detox-android/com/wix/detox/20.30.0/detox-20.30.0-sources.jar.sha256 +1 -0
  5. package/Detox-android/com/wix/detox/20.30.0/detox-20.30.0-sources.jar.sha512 +1 -0
  6. package/Detox-android/com/wix/detox/20.30.0/detox-20.30.0.aar +0 -0
  7. package/Detox-android/com/wix/detox/20.30.0/detox-20.30.0.aar.md5 +1 -0
  8. package/Detox-android/com/wix/detox/20.30.0/detox-20.30.0.aar.sha1 +1 -0
  9. package/Detox-android/com/wix/detox/20.30.0/detox-20.30.0.aar.sha256 +1 -0
  10. package/Detox-android/com/wix/detox/20.30.0/detox-20.30.0.aar.sha512 +1 -0
  11. package/Detox-android/com/wix/detox/{20.27.7-smoke.0/detox-20.27.7-smoke.0.pom → 20.30.0/detox-20.30.0.pom} +2 -2
  12. package/Detox-android/com/wix/detox/20.30.0/detox-20.30.0.pom.md5 +1 -0
  13. package/Detox-android/com/wix/detox/20.30.0/detox-20.30.0.pom.sha1 +1 -0
  14. package/Detox-android/com/wix/detox/20.30.0/detox-20.30.0.pom.sha256 +1 -0
  15. package/Detox-android/com/wix/detox/20.30.0/detox-20.30.0.pom.sha512 +1 -0
  16. package/Detox-android/com/wix/detox/maven-metadata.xml +4 -4
  17. package/Detox-android/com/wix/detox/maven-metadata.xml.md5 +1 -1
  18. package/Detox-android/com/wix/detox/maven-metadata.xml.sha1 +1 -1
  19. package/Detox-android/com/wix/detox/maven-metadata.xml.sha256 +1 -1
  20. package/Detox-android/com/wix/detox/maven-metadata.xml.sha512 +1 -1
  21. package/Detox-ios-framework.tbz +0 -0
  22. package/Detox-ios-src.tbz +0 -0
  23. package/Detox-ios-xcuitest.tbz +0 -0
  24. package/android/build.gradle +5 -5
  25. package/android/detox/proguard-rules-app.pro +3 -0
  26. package/android/detox/src/full/java/com/wix/detox/espresso/EspressoDetox.java +54 -3
  27. package/android/detox/src/full/java/com/wix/detox/espresso/UiAutomatorHelper.java +11 -0
  28. package/android/detox/src/full/java/com/wix/detox/espresso/matcher/IsDisplayingAtLeastDetoxMatcher.kt +2 -2
  29. package/android/detox/src/full/java/com/wix/detox/espresso/web/DetoxWebAtomMatcher.java +4 -7
  30. package/android/detox/src/full/java/com/wix/detox/espresso/web/WebElement.java +0 -5
  31. package/android/detox/src/full/java/com/wix/detox/espresso/web/WebViewElement.java +33 -8
  32. package/android/detox/src/full/java/com/wix/detox/reactnative/ReactNativeExtension.kt +6 -11
  33. package/android/detox/src/full/java/com/wix/detox/reactnative/ReactNativeInfo.kt +4 -11
  34. package/android/detox/src/full/java/com/wix/detox/reactnative/idlingresources/DetoxIdlingResource.kt +42 -0
  35. package/android/detox/src/full/java/com/wix/detox/reactnative/idlingresources/ReactNativeIdlingResources.kt +145 -0
  36. package/android/detox/src/full/java/com/wix/detox/reactnative/idlingresources/animations/AnimatedModuleIdlingResource.kt +61 -0
  37. package/android/detox/src/full/java/com/wix/detox/reactnative/idlingresources/bridge/BridgeIdlingResource.kt +72 -0
  38. package/android/detox/src/full/java/com/wix/detox/reactnative/idlingresources/factory/DetoxIdlingResourceFactory.kt +32 -0
  39. package/android/detox/src/full/java/com/wix/detox/reactnative/idlingresources/factory/IdlingResourcesName.kt +10 -0
  40. package/android/detox/src/full/java/com/wix/detox/reactnative/idlingresources/factory/LooperName.kt +6 -0
  41. package/android/detox/src/full/java/com/wix/detox/reactnative/idlingresources/looper/MQThreadsReflector.kt +47 -0
  42. package/android/detox/src/full/java/com/wix/detox/reactnative/idlingresources/network/NetworkIdlingResource.kt +105 -0
  43. package/android/detox/src/full/java/com/wix/detox/reactnative/idlingresources/{NetworkingModuleReflected.kt → network/NetworkingModuleReflected.kt} +1 -1
  44. package/android/detox/src/full/java/com/wix/detox/reactnative/idlingresources/{AsyncStorageIdlingResource.kt → storage/AsyncStorageIdlingResource.kt} +33 -35
  45. package/android/detox/src/full/java/com/wix/detox/reactnative/idlingresources/{SerialExecutorReflected.kt → storage/SerialExecutorReflected.kt} +1 -1
  46. package/android/detox/src/full/java/com/wix/detox/reactnative/idlingresources/timers/TimersIdlingResource.kt +21 -19
  47. package/android/detox/src/full/java/com/wix/detox/reactnative/idlingresources/uimodule/UIManagerModuleReflected.kt +19 -27
  48. package/android/detox/src/full/java/com/wix/detox/reactnative/idlingresources/uimodule/UIModuleIdlingResource.kt +5 -17
  49. package/android/detox/src/testFull/java/com/wix/detox/reactnative/idlingresources/AsyncStorageIdlingResourceTest.kt +248 -0
  50. package/android/detox/src/testFull/java/com/wix/detox/reactnative/idlingresources/NetworkIdlingResourcesTest.kt +5 -1
  51. package/android/detox/src/testFull/java/com/wix/detox/reactnative/idlingresources/SerialExecutorReflectedSpec.kt +1 -0
  52. package/android/detox/src/testFull/java/com/wix/detox/reactnative/idlingresources/timers/TimersIdlingResourceTest.kt +212 -0
  53. package/android/gradle/wrapper/gradle-wrapper.properties +1 -1
  54. package/android/rninfo.gradle +31 -24
  55. package/android/settings.gradle +11 -6
  56. package/detox.d.ts +39 -0
  57. package/package.json +11 -8
  58. package/scripts/postinstall.js +2 -2
  59. package/scripts/updateGradle.js +41 -8
  60. package/src/DetoxWorker.js +11 -6
  61. package/src/android/espressoapi/EspressoDetox.js +83 -0
  62. package/src/copilot/DetoxCopilot.js +3 -15
  63. package/src/copilot/detoxCopilotFrameworkDriver.js +27 -15
  64. package/src/devices/runtime/RuntimeDevice.js +11 -0
  65. package/src/devices/runtime/drivers/DeviceDriverBase.js +8 -0
  66. package/src/devices/runtime/drivers/android/AndroidDriver.js +16 -0
  67. package/src/devices/runtime/drivers/ios/SimulatorDriver.js +35 -0
  68. package/src/utils/assertArgument.js +9 -0
  69. package/src/utils/invocationTraceDescriptions.js +1 -0
  70. package/src/utils/mapDeviceLongPressArguments.js +56 -0
  71. package/Detox-android/com/wix/detox/20.27.7-smoke.0/detox-20.27.7-smoke.0-sources.jar.md5 +0 -1
  72. package/Detox-android/com/wix/detox/20.27.7-smoke.0/detox-20.27.7-smoke.0-sources.jar.sha1 +0 -1
  73. package/Detox-android/com/wix/detox/20.27.7-smoke.0/detox-20.27.7-smoke.0-sources.jar.sha256 +0 -1
  74. package/Detox-android/com/wix/detox/20.27.7-smoke.0/detox-20.27.7-smoke.0-sources.jar.sha512 +0 -1
  75. package/Detox-android/com/wix/detox/20.27.7-smoke.0/detox-20.27.7-smoke.0.aar +0 -0
  76. package/Detox-android/com/wix/detox/20.27.7-smoke.0/detox-20.27.7-smoke.0.aar.md5 +0 -1
  77. package/Detox-android/com/wix/detox/20.27.7-smoke.0/detox-20.27.7-smoke.0.aar.sha1 +0 -1
  78. package/Detox-android/com/wix/detox/20.27.7-smoke.0/detox-20.27.7-smoke.0.aar.sha256 +0 -1
  79. package/Detox-android/com/wix/detox/20.27.7-smoke.0/detox-20.27.7-smoke.0.aar.sha512 +0 -1
  80. package/Detox-android/com/wix/detox/20.27.7-smoke.0/detox-20.27.7-smoke.0.pom.md5 +0 -1
  81. package/Detox-android/com/wix/detox/20.27.7-smoke.0/detox-20.27.7-smoke.0.pom.sha1 +0 -1
  82. package/Detox-android/com/wix/detox/20.27.7-smoke.0/detox-20.27.7-smoke.0.pom.sha256 +0 -1
  83. package/Detox-android/com/wix/detox/20.27.7-smoke.0/detox-20.27.7-smoke.0.pom.sha512 +0 -1
  84. package/Detox-android/com/wix/detox-legacy/20.27.7-smoke.0/detox-legacy-20.27.7-smoke.0-sources.jar +0 -0
  85. package/Detox-android/com/wix/detox-legacy/20.27.7-smoke.0/detox-legacy-20.27.7-smoke.0-sources.jar.md5 +0 -1
  86. package/Detox-android/com/wix/detox-legacy/20.27.7-smoke.0/detox-legacy-20.27.7-smoke.0-sources.jar.sha1 +0 -1
  87. package/Detox-android/com/wix/detox-legacy/20.27.7-smoke.0/detox-legacy-20.27.7-smoke.0-sources.jar.sha256 +0 -1
  88. package/Detox-android/com/wix/detox-legacy/20.27.7-smoke.0/detox-legacy-20.27.7-smoke.0-sources.jar.sha512 +0 -1
  89. package/Detox-android/com/wix/detox-legacy/20.27.7-smoke.0/detox-legacy-20.27.7-smoke.0.aar +0 -0
  90. package/Detox-android/com/wix/detox-legacy/20.27.7-smoke.0/detox-legacy-20.27.7-smoke.0.aar.md5 +0 -1
  91. package/Detox-android/com/wix/detox-legacy/20.27.7-smoke.0/detox-legacy-20.27.7-smoke.0.aar.sha1 +0 -1
  92. package/Detox-android/com/wix/detox-legacy/20.27.7-smoke.0/detox-legacy-20.27.7-smoke.0.aar.sha256 +0 -1
  93. package/Detox-android/com/wix/detox-legacy/20.27.7-smoke.0/detox-legacy-20.27.7-smoke.0.aar.sha512 +0 -1
  94. package/Detox-android/com/wix/detox-legacy/20.27.7-smoke.0/detox-legacy-20.27.7-smoke.0.pom +0 -100
  95. package/Detox-android/com/wix/detox-legacy/20.27.7-smoke.0/detox-legacy-20.27.7-smoke.0.pom.md5 +0 -1
  96. package/Detox-android/com/wix/detox-legacy/20.27.7-smoke.0/detox-legacy-20.27.7-smoke.0.pom.sha1 +0 -1
  97. package/Detox-android/com/wix/detox-legacy/20.27.7-smoke.0/detox-legacy-20.27.7-smoke.0.pom.sha256 +0 -1
  98. package/Detox-android/com/wix/detox-legacy/20.27.7-smoke.0/detox-legacy-20.27.7-smoke.0.pom.sha512 +0 -1
  99. package/Detox-android/com/wix/detox-legacy/maven-metadata.xml +0 -13
  100. package/Detox-android/com/wix/detox-legacy/maven-metadata.xml.md5 +0 -1
  101. package/Detox-android/com/wix/detox-legacy/maven-metadata.xml.sha1 +0 -1
  102. package/Detox-android/com/wix/detox-legacy/maven-metadata.xml.sha256 +0 -1
  103. package/Detox-android/com/wix/detox-legacy/maven-metadata.xml.sha512 +0 -1
  104. package/android/detox/src/full/java/com/wix/detox/espresso/web/DetoxDriverAtoms.java +0 -642
  105. package/android/detox/src/full/java/com/wix/detox/reactnative/ReactNativeIdlingResources.kt +0 -229
  106. package/android/detox/src/full/java/com/wix/detox/reactnative/idlingresources/AnimatedModuleIdlingResource.java +0 -215
  107. package/android/detox/src/full/java/com/wix/detox/reactnative/idlingresources/BridgeIdlingResource.java +0 -94
  108. package/android/detox/src/full/java/com/wix/detox/reactnative/idlingresources/DetoxBaseIdlingResource.java +0 -29
  109. package/android/detox/src/full/java/com/wix/detox/reactnative/idlingresources/NetworkIdlingResource.java +0 -134
  110. package/android/detox/src/full/java/com/wix/detox/reactnative/idlingresources/timers/DelegatedIdleInterrogationStrategy.kt +0 -23
  111. package/android/detox/src/full/java/com/wix/detox/reactnative/idlingresources/timers/IdleInterrogationStrategy.kt +0 -16
  112. package/android/detox/src/full/java/com/wix/detox/reactnative/idlingresources/uimodule/RN66Workaround.kt +0 -71
  113. package/android/detox/src/testFull/java/com/wix/detox/reactnative/idlingresources/AsyncStorageIdlingResourceSpec.kt +0 -227
  114. package/android/detox/src/testFull/java/com/wix/detox/reactnative/idlingresources/timers/DelegatedIdleInterrogationStrategySpec.kt +0 -47
  115. package/android/detox/src/testFull/java/com/wix/detox/reactnative/idlingresources/timers/TimersIdlingResourceSpec.kt +0 -189
@@ -1,11 +1,24 @@
1
1
  import groovy.json.JsonSlurper
2
2
 
3
- def getRNVersion = { workingDir ->
4
- println("RNInfo: workingDir=$workingDir")
3
+ def getRNPackageJsonDir() {
4
+ def currentDir = rootDir
5
+ while (currentDir != null) {
6
+ def nodeModulesDir = new File(currentDir, "node_modules/react-native")
7
+ def file = new File(nodeModulesDir, 'package.json')
8
+ if (file.exists()) {
9
+ return file.path
10
+ }
11
+ currentDir = currentDir.parentFile
12
+ }
13
+ throw new GradleException("Unable to find module: $moduleName")
14
+ }
15
+
16
+ def getRNVersion = { rnPackageJsonPath ->
17
+ println("RNInfo: package.json=$rnPackageJsonPath")
5
18
  def jsonSlurper = new JsonSlurper()
6
- def packageFile = "$workingDir/../node_modules/react-native/package.json"
7
- println("RNInfo: reading $packageFile")
8
- Map<String, Object> packageJSON = jsonSlurper.parse(new File(packageFile))
19
+ def packageFile =
20
+ println("RNInfo: reading $rnPackageJsonPath")
21
+ Map<String, Object> packageJSON = jsonSlurper.parse(new File(rnPackageJsonPath))
9
22
  String rnVersion = packageJSON.get('version')
10
23
  return rnVersion
11
24
  }
@@ -15,24 +28,18 @@ def getMajorVersionInternal = { semanticVersion ->
15
28
  return rnVersionMajor
16
29
  }
17
30
 
18
- ext.getRnMajorVersion = { workingDir ->
19
- String rnVersion = getRNVersion(workingDir)
20
- Integer rnVersionMajor = getMajorVersionInternal(rnVersion)
21
- return rnVersionMajor
22
- }
23
-
24
- def rnVersion = getRNVersion(rootDir)
31
+ def rnVersion = getRNVersion(getRNPackageJsonDir())
25
32
  def rnMajorVer = getMajorVersionInternal(rnVersion)
26
- if (hasProperty('project')) {
27
- println "[$project] RNInfo: detected React Native version: $rnVersion (major=$rnMajorVer)"
33
+ println "RNInfo: detected React Native version: $rnVersion (major=$rnMajorVer)"
28
34
 
29
- project.ext.rnInfo = [
30
- version : rnVersion,
31
- majorVersion : rnMajorVer,
32
- isRN69OrHigher: rnMajorVer >= 69,
33
- isRN70OrHigher: rnMajorVer >= 70,
34
- isRN71OrHigher: rnMajorVer >= 71,
35
- isRN72OrHigher: rnMajorVer >= 72,
36
- isRN73OrHigher: rnMajorVer >= 73,
37
- ]
38
- }
35
+ ext.rnInfo = [
36
+ version : rnVersion,
37
+ majorVersion : rnMajorVer,
38
+ isRN69OrHigher: rnMajorVer >= 69,
39
+ isRN70OrHigher: rnMajorVer >= 70,
40
+ isRN71OrHigher: rnMajorVer >= 71,
41
+ isRN72OrHigher: rnMajorVer >= 72,
42
+ isRN73OrHigher: rnMajorVer >= 73,
43
+ isRN74OrHigher: rnMajorVer >= 74,
44
+ isRN75OrHigher: rnMajorVer >= 75,
45
+ ]
@@ -1,14 +1,19 @@
1
+ // RN75+_BLOCK_START
2
+ pluginManagement { includeBuild("../node_modules/@react-native/gradle-plugin") }
3
+ plugins { id("com.facebook.react.settings") }
4
+ extensions.configure(com.facebook.react.ReactSettingsExtension){ ex -> ex.autolinkLibrariesFromCommand() }
5
+ // RN75+_BLOCK_END
6
+
7
+
1
8
  apply from: '../android/rninfo.gradle'
2
9
  include ':detox'
3
10
 
4
11
  println("RNInfo: rootDir=$rootDir")
12
+ println "[settings] RNInfo: detected React Native version: (major=${ext.rnInfo.version})"
5
13
 
6
- def rnMajorVer = getRnMajorVersion(rootDir)
7
- println "[settings] RNInfo: detected React Native version: (major=$rnMajorVer)"
8
14
 
9
- if (rnMajorVer < 72) {
10
- includeBuild('../node_modules/react-native-gradle-plugin')
11
- } else {
15
+ if (ext.rnInfo.isRN72OrHigher) {
12
16
  includeBuild('../node_modules/@react-native/gradle-plugin')
17
+ } else {
18
+ includeBuild('../node_modules/react-native-gradle-plugin')
13
19
  }
14
-
package/detox.d.ts CHANGED
@@ -784,6 +784,45 @@ declare global {
784
784
  */
785
785
  setOrientation(orientation: Orientation): Promise<void>;
786
786
 
787
+ /**
788
+ * Perform a tap at arbitrary coordinates on the device's screen.
789
+ * @param point Coordinates in the element's coordinate space. Optional. defaults: x: 100, y: 100
790
+ * @param shouldIgnoreStatusBar Coordinates will be measured starting from under the status bar. this param will affect only in Android tests. Optional. default: true
791
+ * @example await device.tap();
792
+ * @example await device.tap({ x: 100, y: 150 }, false);
793
+ * @example await device.tap({ x: 100, y: 150 });
794
+ * @example await device.tap(false);
795
+ */
796
+ tap(): Promise<void>;
797
+ tap(point: Point2D): Promise<void>;
798
+ tap(point: Point2D, shouldIgnoreStatusBar: boolean): Promise<void>;
799
+ tap(shouldIgnoreStatusBar: boolean): Promise<void>;
800
+
801
+ /**
802
+ * Perform a long press at arbitrary coordinates on the device's screen. Custom press duration if needed.
803
+ * @param point Coordinates in the device's coordinate space. Optional. defaults: x: 100, y: 100
804
+ * @param duration Custom press duration time, in milliseconds. Optional (defaults to the standard long-press duration for Android and 1000 milliseconds for ios).
805
+ * Custom durations should be used cautiously, as they can affect test consistency and user experience expectations.
806
+ * They are typically necessary when testing components that behave differently from the platform's defaults or when simulating unique user interactions.
807
+ * @param shouldIgnoreStatusBar Coordinates will be measured starting from under the status bar. this param will affect only in Android tests. Optional. default: true
808
+ * @example await device.longPress();
809
+ * @example await device.longPress({ x: 100, y: 150 }, 2000, false);
810
+ * @example await device.longPress({ x: 100, y: 150 }, 2000);
811
+ * @example await device.longPress(2000, false);
812
+ * @example await device.longPress({ x: 100, y: 150 }, false);
813
+ * @example await device.longPress({ x: 100, y: 150 });
814
+ * @example await device.longPress(2000);
815
+ * @example await device.longPress(false);
816
+ */
817
+ longPress(): Promise<void>;
818
+ longPress(point: Point2D, duration: number, shouldIgnoreStatusBar: boolean): Promise<void>;
819
+ longPress(point: Point2D, duration: number): Promise<void>;
820
+ longPress(duration: number, shouldIgnoreStatusBar: boolean): Promise<void>;
821
+ longPress(point: Point2D, shouldIgnoreStatusBar: boolean): Promise<void>;
822
+ longPress(point: Point2D): Promise<void>;
823
+ longPress(duration: number): Promise<void>;
824
+ longPress(shouldIgnoreStatusBar: boolean): Promise<void>;
825
+
787
826
  /**
788
827
  * Sets the simulator/emulator location to the given latitude and longitude.
789
828
  *
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.27.7-smoke.0",
4
+ "version": "20.31.0",
5
5
  "bin": {
6
6
  "detox": "local-cli/cli.js"
7
7
  },
@@ -34,10 +34,13 @@
34
34
  "postinstall": "node scripts/postinstall.js"
35
35
  },
36
36
  "devDependencies": {
37
- "@react-native/babel-preset": "0.73.19",
38
- "@react-native/eslint-config": "^0.73.2",
39
- "@react-native/metro-config": "^0.73.3",
40
- "@react-native/typescript-config": "0.73.1",
37
+ "@react-native-community/cli": "15.0.1",
38
+ "@react-native-community/cli-platform-android": "15.0.1",
39
+ "@react-native-community/cli-platform-ios": "15.0.1",
40
+ "@react-native/babel-preset": "0.76.3",
41
+ "@react-native/eslint-config": "0.76.3",
42
+ "@react-native/metro-config": "0.76.3",
43
+ "@react-native/typescript-config": "0.76.3",
41
44
  "@tsconfig/react-native": "^3.0.0",
42
45
  "@types/bunyan": "^1.8.8",
43
46
  "@types/child-process-promise": "^2.2.1",
@@ -59,7 +62,7 @@
59
62
  "jest-allure2-reporter": "^2.0.0-beta.18",
60
63
  "metro-react-native-babel-preset": "0.76.8",
61
64
  "prettier": "^3.1.1",
62
- "react-native": "0.73.2",
65
+ "react-native": "0.76.3",
63
66
  "react-native-codegen": "^0.0.8",
64
67
  "typescript": "^5.3.3",
65
68
  "wtfnode": "^0.9.1"
@@ -71,7 +74,7 @@
71
74
  "caf": "^15.0.1",
72
75
  "chalk": "^4.0.0",
73
76
  "child-process-promise": "^2.2.0",
74
- "detox-copilot": "^0.0.23",
77
+ "detox-copilot": "^0.0.27",
75
78
  "execa": "^5.1.1",
76
79
  "find-up": "^5.0.0",
77
80
  "fs-extra": "^11.0.0",
@@ -116,5 +119,5 @@
116
119
  "browserslist": [
117
120
  "node 14"
118
121
  ],
119
- "gitHead": "339b51c8b014686628fff6f695b3d79ccf555a12"
122
+ "gitHead": "89a3f0efbac69cd37db886a53f712d51db8f857d"
120
123
  }
@@ -1,6 +1,6 @@
1
1
  const { platform, env } = process;
2
2
 
3
- const { setGradleVersionByRNVersion } = require('./updateGradle');
3
+ const { patchGradleByRNVersion } = require('./updateGradle');
4
4
 
5
5
  const isDarwin = platform === 'darwin';
6
6
  const shouldInstallDetox = !env.DETOX_DISABLE_POSTINSTALL;
@@ -12,4 +12,4 @@ if (isDarwin && shouldInstallDetox) {
12
12
  execFileSync(`${__dirname}/build_local_xcuitest.ios.sh`, { stdio: 'inherit' });
13
13
  }
14
14
 
15
- setGradleVersionByRNVersion();
15
+ patchGradleByRNVersion();
@@ -6,6 +6,12 @@ const rnMinor = require('../src/utils/rn-consts/rn-consts').rnVersion.minor;
6
6
  function getGradleVersionByRNVersion() {
7
7
  switch (rnMinor) {
8
8
  default:
9
+ return '8.10.2';
10
+ case '75':
11
+ return '8.8';
12
+ case '74':
13
+ return '8.6';
14
+ case '73':
9
15
  return '8.3';
10
16
  case '72':
11
17
  return '8.0';
@@ -17,19 +23,46 @@ function getGradleVersionByRNVersion() {
17
23
  /**
18
24
  * Update the Gradle wrapper to the version that matches the React Native version.
19
25
  */
20
- function setGradleVersionByRNVersion() {
21
- const gradleVersion = getGradleVersionByRNVersion();
22
- updateGradleWrapperSync(gradleVersion);
26
+ function patchGradleByRNVersion() {
27
+ updateGradleWrapperSync();
28
+ patchSettingsGradle();
29
+ }
30
+
31
+ /**
32
+ * In RN75 and above the settings.gradle file should contain the following lines. We can't wrap them in 'if' statement
33
+ * because they should be the first line in the settings file. This patch could be safely removed after dropping support
34
+ * for RN74.
35
+ */
36
+ function patchSettingsGradle() {
37
+ if (parseInt(rnMinor) >= 75) {
38
+ return;
39
+ }
40
+
41
+ const settingsGradlePath = path.join(process.cwd(), 'android', 'settings.gradle');
42
+ console.log(`Patching settings.gradle. File: ${settingsGradlePath}`);
43
+
44
+ try {
45
+ let data = fs.readFileSync(settingsGradlePath, 'utf8');
46
+ const blockRegex = /\/\/ RN75\+_BLOCK_START[\s\S]*?\/\/ RN75\+_BLOCK_END/g;
47
+
48
+ // Replace the block with an empty string
49
+ const updatedData = data.replace(blockRegex, '');
50
+
51
+ fs.writeFileSync(settingsGradlePath, updatedData, 'utf8');
52
+ console.log('settings.gradle patched successfully.');
53
+ } catch (err) {
54
+ console.error('Error:', err);
55
+ }
23
56
  }
24
57
 
25
58
  /**
26
59
  * Update the Gradle wrapper to the specified version.
27
- *
28
- * @param {string} newVersion - the new Gradle wrapper version
29
60
  */
30
- function updateGradleWrapperSync(newVersion) {
61
+ function updateGradleWrapperSync() {
62
+ const newVersion = getGradleVersionByRNVersion();
63
+
31
64
  const gradleWrapperPath = path.join(process.cwd(), 'android', 'gradle', 'wrapper', 'gradle-wrapper.properties');
32
- console.log(`Updating Gradle wrapper to version${newVersion}. File: ${gradleWrapperPath}`);
65
+ console.log(`Updating Gradle wrapper to version$ {newVersion}. File: ${gradleWrapperPath}`);
33
66
 
34
67
  try {
35
68
  let data = fs.readFileSync(gradleWrapperPath, 'utf8');
@@ -43,5 +76,5 @@ function updateGradleWrapperSync(newVersion) {
43
76
  }
44
77
 
45
78
  module.exports = {
46
- setGradleVersionByRNVersion
79
+ patchGradleByRNVersion: patchGradleByRNVersion
47
80
  };
@@ -1,4 +1,5 @@
1
1
  const CAF = require('caf');
2
+ const copilot = require('detox-copilot').default;
2
3
  const _ = require('lodash');
3
4
 
4
5
  const Client = require('./client/Client');
@@ -62,7 +63,7 @@ class DetoxWorker {
62
63
  /** @type {Detox.SystemFacade} */
63
64
  this.system = null;
64
65
  /** @type {Detox.DetoxCopilotFacade} */
65
- this.copilot = null;
66
+ this.copilot = new DetoxCopilot();
66
67
 
67
68
  this._deviceCookie = null;
68
69
 
@@ -126,8 +127,6 @@ class DetoxWorker {
126
127
  runtimeDeviceFactory,
127
128
  } = environmentFactory.createFactories(deviceConfig);
128
129
 
129
- this.copilot = new DetoxCopilot();
130
-
131
130
  const envValidator = envValidatorFactory.createValidator();
132
131
  yield envValidator.validate();
133
132
 
@@ -226,9 +225,10 @@ class DetoxWorker {
226
225
  yield this._artifactsManager.onRunDescribeStart(...args);
227
226
  };
228
227
 
229
- onTestStart = function* (_signal, testSummary) {
230
- // Copilot is reset before each test to ensure a clean state
231
- this.copilot.resetIfNeeded();
228
+ onTestStart = function* (_signal, testSummary){
229
+ if (copilot.isInitialized()) {
230
+ copilot.start();
231
+ }
232
232
 
233
233
  this._validateTestSummary('beforeEach', testSummary);
234
234
 
@@ -257,6 +257,11 @@ class DetoxWorker {
257
257
  pendingRequests: testSummary.timedOut,
258
258
  testName: testSummary.fullName,
259
259
  });
260
+
261
+ if (copilot.isInitialized()) {
262
+ // In case of failure, pass false to copilot, so temporary cache is not saved
263
+ copilot.end(testSummary.status === 'passed');
264
+ }
260
265
  };
261
266
 
262
267
  onRunDescribeFinish = function* (_signal, ...args) {
@@ -73,6 +73,89 @@ class EspressoDetox {
73
73
  };
74
74
  }
75
75
 
76
+ static tap(x, y, shouldIgnoreStatusBar) {
77
+ if (typeof x !== "number") throw new Error("x should be a number, but got " + (x + (" (" + (typeof x + ")"))));
78
+ if (typeof y !== "number") throw new Error("y should be a number, but got " + (y + (" (" + (typeof y + ")"))));
79
+ if (typeof shouldIgnoreStatusBar !== "boolean") throw new Error("shouldIgnoreStatusBar should be a boolean, but got " + (shouldIgnoreStatusBar + (" (" + (typeof shouldIgnoreStatusBar + ")"))));
80
+ return {
81
+ target: {
82
+ type: "Class",
83
+ value: "com.wix.detox.espresso.EspressoDetox"
84
+ },
85
+ method: "tap",
86
+ args: [{
87
+ type: "Integer",
88
+ value: x
89
+ }, {
90
+ type: "Integer",
91
+ value: y
92
+ }, {
93
+ type: "boolean",
94
+ value: shouldIgnoreStatusBar
95
+ }]
96
+ };
97
+ }
98
+
99
+ static longPress(x, y, shouldIgnoreStatusBar) {
100
+ function longPress3(x, y, shouldIgnoreStatusBar) {
101
+ if (typeof x !== "number") throw new Error("x should be a number, but got " + (x + (" (" + (typeof x + ")"))));
102
+ if (typeof y !== "number") throw new Error("y should be a number, but got " + (y + (" (" + (typeof y + ")"))));
103
+ if (typeof shouldIgnoreStatusBar !== "boolean") throw new Error("shouldIgnoreStatusBar should be a boolean, but got " + (shouldIgnoreStatusBar + (" (" + (typeof shouldIgnoreStatusBar + ")"))));
104
+ return {
105
+ target: {
106
+ type: "Class",
107
+ value: "com.wix.detox.espresso.EspressoDetox"
108
+ },
109
+ method: "longPress",
110
+ args: [{
111
+ type: "Integer",
112
+ value: x
113
+ }, {
114
+ type: "Integer",
115
+ value: y
116
+ }, {
117
+ type: "boolean",
118
+ value: shouldIgnoreStatusBar
119
+ }]
120
+ };
121
+ }
122
+
123
+ function longPress4(x, y, duration, shouldIgnoreStatusBar) {
124
+ if (typeof x !== "number") throw new Error("x should be a number, but got " + (x + (" (" + (typeof x + ")"))));
125
+ if (typeof y !== "number") throw new Error("y should be a number, but got " + (y + (" (" + (typeof y + ")"))));
126
+ if (typeof duration !== "number") throw new Error("duration should be a number, but got " + (duration + (" (" + (typeof duration + ")"))));
127
+ if (typeof shouldIgnoreStatusBar !== "boolean") throw new Error("shouldIgnoreStatusBar should be a boolean, but got " + (shouldIgnoreStatusBar + (" (" + (typeof shouldIgnoreStatusBar + ")"))));
128
+ return {
129
+ target: {
130
+ type: "Class",
131
+ value: "com.wix.detox.espresso.EspressoDetox"
132
+ },
133
+ method: "longPress",
134
+ args: [{
135
+ type: "Integer",
136
+ value: x
137
+ }, {
138
+ type: "Integer",
139
+ value: y
140
+ }, {
141
+ type: "Integer",
142
+ value: duration
143
+ }, {
144
+ type: "boolean",
145
+ value: shouldIgnoreStatusBar
146
+ }]
147
+ };
148
+ }
149
+
150
+ if (arguments.length === 3) {
151
+ return longPress3.apply(null, arguments);
152
+ }
153
+
154
+ if (arguments.length === 4) {
155
+ return longPress4.apply(null, arguments);
156
+ }
157
+ }
158
+
76
159
  }
77
160
 
78
161
  module.exports = EspressoDetox;
@@ -2,27 +2,15 @@ const copilot = require('detox-copilot').default;
2
2
 
3
3
  const detoxCopilotFrameworkDriver = require('./detoxCopilotFrameworkDriver');
4
4
 
5
+ /**
6
+ * @typedef {Object} Detox.DetoxCopilotFacade
7
+ */
5
8
  class DetoxCopilot {
6
- constructor() {
7
- this.isInitialized = false;
8
- }
9
-
10
9
  init(promptHandler) {
11
10
  copilot.init({
12
11
  frameworkDriver: detoxCopilotFrameworkDriver,
13
12
  promptHandler: promptHandler
14
13
  });
15
-
16
- this.isInitialized = true;
17
- }
18
-
19
- resetIfNeeded() {
20
- if (!this.isInitialized) {
21
- // Copilot is not initialized, nothing to reset
22
- return;
23
- }
24
-
25
- copilot.reset();
26
14
  }
27
15
 
28
16
  perform(...steps) {
@@ -18,7 +18,7 @@ const detoxCopilotFrameworkDriver = {
18
18
  },
19
19
  {
20
20
  signature: 'by.text(text: string)',
21
- description: 'Matches elements by their text.',
21
+ description: 'Matches elements by their text (value).',
22
22
  example: "element(by.text('Login'))",
23
23
  guidelines: ['Prefer test IDs over text matchers when possible.'],
24
24
  },
@@ -34,6 +34,12 @@ const detoxCopilotFrameworkDriver = {
34
34
  example: "element(by.id('listItem')).atIndex(2)",
35
35
  guidelines: ['Use when multiple elements match the same matcher.'],
36
36
  },
37
+ {
38
+ signature: 'by.label(label: string)',
39
+ description: 'Match elements with the specified label.',
40
+ example: "element(by.label('Tuesday, 1 October'));",
41
+ guidelines: ['Use when there are no other identifiers, such as for date pickers to select specific days.'],
42
+ },
37
43
  ],
38
44
  },
39
45
  {
@@ -106,7 +112,7 @@ const detoxCopilotFrameworkDriver = {
106
112
  ],
107
113
  },
108
114
  {
109
- signature: 'waitFor(element: Matcher)..toBeVisible().whileElement(element: Matcher).scroll(offset: number, direction: string)',
115
+ signature: 'waitFor(element: Matcher).toBeVisible(percent?: number).whileElement(element: Matcher).scroll(offset: number, direction: string)',
110
116
  description: 'Continuously performs an action while waiting for an expectation to be fulfilled.',
111
117
  example: `
112
118
  await waitFor(element(by.text('Load More')))
@@ -185,9 +191,13 @@ jestExpect(attributes.text).toBe('Tap Me');`,
185
191
  title: 'Assertions',
186
192
  items: [
187
193
  {
188
- signature: 'toBeVisible()',
189
- description: 'Asserts that the element is visible.',
190
- example: "await expect(element(by.id('loginButton'))).toBeVisible();",
194
+ signature: 'toBeVisible(percent?: number)',
195
+ description: 'Asserts that the element is visible with at-least the specified percentage. Default percent is 75%.',
196
+ example: "await expect(element(by.id('loginButton'))).toBeVisible(38);",
197
+ guidelines: [
198
+ 'Use the default visibility percent unless a different percentage is required.',
199
+ 'If a percentage value is provided, use the exact percentage required for the test.',
200
+ ],
191
201
  },
192
202
  {
193
203
  signature: 'toExist()',
@@ -377,8 +387,8 @@ await device.launchApp({ launchArgs: { someLaunchArg: 1234 } });`,
377
387
  items: [
378
388
  {
379
389
  signature: 'web.element(matcher: Matcher)',
380
- description: 'Selects an element within a web view. Use when there is only one web view on the screen.',
381
- example: `
390
+ description: 'Selects an element within a web view (`WKWebView` or `RNCWebView`). Use when there is only one web view on the screen.',
391
+ example: `
382
392
  await web.element(by.web.id('email')).typeText('test@example.com');
383
393
  await web.element(by.web.id('password')).typeText('password123');
384
394
  await web.element(by.web.id('login-button')).tap();
@@ -387,19 +397,20 @@ await web.element(by.web.id('login-button')).tap();
387
397
  'The web view may take time to load; add a delay using `await new Promise(resolve => setTimeout(resolve, milliseconds));` before the first interaction. This wait should happen only once.',
388
398
  'After the initial wait, you can interact with web elements without additional delays.',
389
399
  'Use `by.web.id` matcher when possible for matching web elements, as it is the most reliable.',
390
- 'Web APIs can only be used with web elements (within web views). Avoid using web APIs for native elements or native APIs for web elements.',
400
+ 'Web APIs can only be used with web elements (within web views). Do not use web APIs for native elements or native APIs for web elements!',
401
+ 'Confirm that you are targeting a web view before using this method.'
391
402
  ],
392
403
  },
393
404
  {
394
405
  signature: 'web(nativeMatcher: NativeMatcher).element(matcher: Matcher)',
395
- description: 'Selects an element within a specific web view matched by a native matcher. Use when there are multiple web views on the screen.',
406
+ description: 'Selects an element within a specific web view (`WKWebView` or `RNCWebView`) matched by a native matcher. Use when there are multiple web views on the screen.',
396
407
  example: `
397
408
  // Wait for the specific web view to appear and load (only once before interacting)
398
409
  await expect(element(by.id('checkout-webview'))).toBeVisible();
399
410
 
400
411
  // Interact with elements within a specific web view
401
412
  const specificWebView = web(by.id('checkout-webview'));
402
-
413
+
403
414
  await specificWebView.element(by.web.id('credit-card-number')).typeText('4111111111111111');
404
415
  await specificWebView.element(by.web.id('expiration-date')).typeText('12/25');
405
416
  await specificWebView.element(by.web.id('cvv')).typeText('123');
@@ -411,6 +422,7 @@ await specificWebView.element(by.web.id('pay-button')).tap();
411
422
  'After the initial wait, you can interact with elements within the web view without additional delays.',
412
423
  'Webview must be matched with `web(nativeMatcher)` (e.g., `web(by.id(..))` instead of `element(by.id(..))`).',
413
424
  'Prefer the basic `web.element()` if only one web view is present on the screen.',
425
+ 'Confirm that you are targeting a web view before using this method.'
414
426
  ],
415
427
  },
416
428
  {
@@ -485,8 +497,8 @@ await secondWebView.element(by.web.id('search-button')).tap();
485
497
  example: `await web.element(by.web.label('Next')).tap();`,
486
498
  guidelines: [
487
499
  'Available on iOS only.',
488
- 'Use when the element has a unique label or aria-label.',
489
- 'Can be used to match buttons and input elements by their inner text content.',
500
+ 'Use when the inner web element has a unique label or aria-label.',
501
+ 'Can be used to match buttons and input elements inside a web view, by their inner text content.',
490
502
  ],
491
503
  },
492
504
  {
@@ -567,13 +579,13 @@ await web.element(by.web.id('email-input')).focus();
567
579
  await web.element(by.web.id('email-input')).typeText('user@example.com');
568
580
  `,
569
581
  guidelines: [
570
- 'Useful for input fields that require focus before typing.',
582
+ 'Useful for input fields in a web view that require focus before typing.',
571
583
  'No need for secured interactions on iOS.',
572
584
  ]
573
585
  },
574
586
  {
575
587
  signature: 'moveCursorToEnd()',
576
- description: 'Moves the input cursor to the end of the element\'s content.',
588
+ description: 'Moves the input cursor in a web view to the end of the element\'s content.',
577
589
  example: `
578
590
  await web.element(by.web.id('message-box')).moveCursorToEnd();
579
591
  await web.element(by.web.id('message-box')).typeText(' Adding more text.');
@@ -581,7 +593,7 @@ await web.element(by.web.id('message-box')).typeText(' Adding more text.');
581
593
  },
582
594
  {
583
595
  signature: 'runScript(script: string, args?: any[])',
584
- description: 'Runs a JavaScript function on the element.',
596
+ description: 'Runs a JavaScript function on the web view element.',
585
597
  example: `
586
598
  // Click an element using a custom script
587
599
  await web.element(by.web.id('hidden-button')).runScript('el => el.click()');
@@ -1,6 +1,7 @@
1
1
  const DetoxRuntimeError = require('../../errors/DetoxRuntimeError');
2
2
  const debug = require('../../utils/debug'); // debug utils, leave here even if unused
3
3
  const log = require('../../utils/logger').child({ cat: 'device' });
4
+ const mapDeviceLongPressArguments = require('../../utils/mapDeviceLongPressArguments');
4
5
  const traceMethods = require('../../utils/traceMethods');
5
6
  const wrapWithStackTraceCutter = require('../../utils/wrapWithStackTraceCutter');
6
7
 
@@ -283,6 +284,16 @@ class RuntimeDevice {
283
284
  await this.deviceDriver.setOrientation(orientation);
284
285
  }
285
286
 
287
+ async tap(point, shouldIgnoreStatusBar) {
288
+ await this.deviceDriver.tap(point, shouldIgnoreStatusBar, this._bundleId);
289
+ }
290
+
291
+ async longPress(arg1, arg2, arg3) {
292
+ let { point, duration, shouldIgnoreStatusBar } = mapDeviceLongPressArguments(arg1, arg2, arg3);
293
+
294
+ await this.deviceDriver.longPress(point, duration, shouldIgnoreStatusBar, this._bundleId);
295
+ }
296
+
286
297
  async setLocation(lat, lon) {
287
298
  lat = String(lat);
288
299
  lon = String(lon);
@@ -51,6 +51,14 @@ class DeviceDriverBase {
51
51
  return '';
52
52
  }
53
53
 
54
+ async tap(_bundleId) {
55
+ return '';
56
+ }
57
+
58
+ async longPress(_bundleId) {
59
+ return '';
60
+ }
61
+
54
62
  async sendToHome() {
55
63
  return '';
56
64
  }
@@ -245,6 +245,22 @@ class AndroidDriver extends DeviceDriverBase {
245
245
  await this.invocationManager.execute(call);
246
246
  }
247
247
 
248
+ async tap(point, shouldIgnoreStatusBar) {
249
+ let x = point?.x ?? 100;
250
+ let y = point?.y ?? 100;
251
+ let _shouldIgnoreStatusBar = shouldIgnoreStatusBar ?? true;
252
+ const call = EspressoDetoxApi.tap(x, y, _shouldIgnoreStatusBar);
253
+ await this.invocationManager.execute(call);
254
+ }
255
+
256
+ async longPress(point, duration, shouldIgnoreStatusBar) {
257
+ let x = point?.x ?? 100;
258
+ let y = point?.y ?? 100;
259
+ let _shouldIgnoreStatusBar = shouldIgnoreStatusBar ?? true;
260
+ const call = duration ? EspressoDetoxApi.longPress(x, y, duration, _shouldIgnoreStatusBar): EspressoDetoxApi.longPress(x, y, _shouldIgnoreStatusBar);
261
+ await this.invocationManager.execute(call);
262
+ }
263
+
248
264
  async generateViewHierarchyXml(shouldInjectTestIds) {
249
265
  const hierarchy = await this.invocationManager.execute(DetoxApi.generateViewHierarchyXml(shouldInjectTestIds));
250
266
  return hierarchy.result;