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.
- 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
- package/Detox-android/com/wix/detox/20.30.0/detox-20.30.0-sources.jar.md5 +1 -0
- package/Detox-android/com/wix/detox/20.30.0/detox-20.30.0-sources.jar.sha1 +1 -0
- package/Detox-android/com/wix/detox/20.30.0/detox-20.30.0-sources.jar.sha256 +1 -0
- package/Detox-android/com/wix/detox/20.30.0/detox-20.30.0-sources.jar.sha512 +1 -0
- package/Detox-android/com/wix/detox/20.30.0/detox-20.30.0.aar +0 -0
- package/Detox-android/com/wix/detox/20.30.0/detox-20.30.0.aar.md5 +1 -0
- package/Detox-android/com/wix/detox/20.30.0/detox-20.30.0.aar.sha1 +1 -0
- package/Detox-android/com/wix/detox/20.30.0/detox-20.30.0.aar.sha256 +1 -0
- package/Detox-android/com/wix/detox/20.30.0/detox-20.30.0.aar.sha512 +1 -0
- 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
- package/Detox-android/com/wix/detox/20.30.0/detox-20.30.0.pom.md5 +1 -0
- package/Detox-android/com/wix/detox/20.30.0/detox-20.30.0.pom.sha1 +1 -0
- package/Detox-android/com/wix/detox/20.30.0/detox-20.30.0.pom.sha256 +1 -0
- package/Detox-android/com/wix/detox/20.30.0/detox-20.30.0.pom.sha512 +1 -0
- package/Detox-android/com/wix/detox/maven-metadata.xml +4 -4
- package/Detox-android/com/wix/detox/maven-metadata.xml.md5 +1 -1
- package/Detox-android/com/wix/detox/maven-metadata.xml.sha1 +1 -1
- package/Detox-android/com/wix/detox/maven-metadata.xml.sha256 +1 -1
- package/Detox-android/com/wix/detox/maven-metadata.xml.sha512 +1 -1
- package/Detox-ios-framework.tbz +0 -0
- package/Detox-ios-src.tbz +0 -0
- package/Detox-ios-xcuitest.tbz +0 -0
- package/android/build.gradle +5 -5
- package/android/detox/proguard-rules-app.pro +3 -0
- package/android/detox/src/full/java/com/wix/detox/espresso/EspressoDetox.java +54 -3
- package/android/detox/src/full/java/com/wix/detox/espresso/UiAutomatorHelper.java +11 -0
- package/android/detox/src/full/java/com/wix/detox/espresso/matcher/IsDisplayingAtLeastDetoxMatcher.kt +2 -2
- package/android/detox/src/full/java/com/wix/detox/espresso/web/DetoxWebAtomMatcher.java +4 -7
- package/android/detox/src/full/java/com/wix/detox/espresso/web/WebElement.java +0 -5
- package/android/detox/src/full/java/com/wix/detox/espresso/web/WebViewElement.java +33 -8
- package/android/detox/src/full/java/com/wix/detox/reactnative/ReactNativeExtension.kt +6 -11
- package/android/detox/src/full/java/com/wix/detox/reactnative/ReactNativeInfo.kt +4 -11
- package/android/detox/src/full/java/com/wix/detox/reactnative/idlingresources/DetoxIdlingResource.kt +42 -0
- package/android/detox/src/full/java/com/wix/detox/reactnative/idlingresources/ReactNativeIdlingResources.kt +145 -0
- package/android/detox/src/full/java/com/wix/detox/reactnative/idlingresources/animations/AnimatedModuleIdlingResource.kt +61 -0
- package/android/detox/src/full/java/com/wix/detox/reactnative/idlingresources/bridge/BridgeIdlingResource.kt +72 -0
- package/android/detox/src/full/java/com/wix/detox/reactnative/idlingresources/factory/DetoxIdlingResourceFactory.kt +32 -0
- package/android/detox/src/full/java/com/wix/detox/reactnative/idlingresources/factory/IdlingResourcesName.kt +10 -0
- package/android/detox/src/full/java/com/wix/detox/reactnative/idlingresources/factory/LooperName.kt +6 -0
- package/android/detox/src/full/java/com/wix/detox/reactnative/idlingresources/looper/MQThreadsReflector.kt +47 -0
- package/android/detox/src/full/java/com/wix/detox/reactnative/idlingresources/network/NetworkIdlingResource.kt +105 -0
- package/android/detox/src/full/java/com/wix/detox/reactnative/idlingresources/{NetworkingModuleReflected.kt → network/NetworkingModuleReflected.kt} +1 -1
- package/android/detox/src/full/java/com/wix/detox/reactnative/idlingresources/{AsyncStorageIdlingResource.kt → storage/AsyncStorageIdlingResource.kt} +33 -35
- package/android/detox/src/full/java/com/wix/detox/reactnative/idlingresources/{SerialExecutorReflected.kt → storage/SerialExecutorReflected.kt} +1 -1
- package/android/detox/src/full/java/com/wix/detox/reactnative/idlingresources/timers/TimersIdlingResource.kt +21 -19
- package/android/detox/src/full/java/com/wix/detox/reactnative/idlingresources/uimodule/UIManagerModuleReflected.kt +19 -27
- package/android/detox/src/full/java/com/wix/detox/reactnative/idlingresources/uimodule/UIModuleIdlingResource.kt +5 -17
- package/android/detox/src/testFull/java/com/wix/detox/reactnative/idlingresources/AsyncStorageIdlingResourceTest.kt +248 -0
- package/android/detox/src/testFull/java/com/wix/detox/reactnative/idlingresources/NetworkIdlingResourcesTest.kt +5 -1
- package/android/detox/src/testFull/java/com/wix/detox/reactnative/idlingresources/SerialExecutorReflectedSpec.kt +1 -0
- package/android/detox/src/testFull/java/com/wix/detox/reactnative/idlingresources/timers/TimersIdlingResourceTest.kt +212 -0
- package/android/gradle/wrapper/gradle-wrapper.properties +1 -1
- package/android/rninfo.gradle +31 -24
- package/android/settings.gradle +11 -6
- package/detox.d.ts +39 -0
- package/package.json +11 -8
- package/scripts/postinstall.js +2 -2
- package/scripts/updateGradle.js +41 -8
- package/src/DetoxWorker.js +11 -6
- package/src/android/espressoapi/EspressoDetox.js +83 -0
- package/src/copilot/DetoxCopilot.js +3 -15
- package/src/copilot/detoxCopilotFrameworkDriver.js +27 -15
- package/src/devices/runtime/RuntimeDevice.js +11 -0
- package/src/devices/runtime/drivers/DeviceDriverBase.js +8 -0
- package/src/devices/runtime/drivers/android/AndroidDriver.js +16 -0
- package/src/devices/runtime/drivers/ios/SimulatorDriver.js +35 -0
- package/src/utils/assertArgument.js +9 -0
- package/src/utils/invocationTraceDescriptions.js +1 -0
- package/src/utils/mapDeviceLongPressArguments.js +56 -0
- package/Detox-android/com/wix/detox/20.27.7-smoke.0/detox-20.27.7-smoke.0-sources.jar.md5 +0 -1
- package/Detox-android/com/wix/detox/20.27.7-smoke.0/detox-20.27.7-smoke.0-sources.jar.sha1 +0 -1
- package/Detox-android/com/wix/detox/20.27.7-smoke.0/detox-20.27.7-smoke.0-sources.jar.sha256 +0 -1
- package/Detox-android/com/wix/detox/20.27.7-smoke.0/detox-20.27.7-smoke.0-sources.jar.sha512 +0 -1
- package/Detox-android/com/wix/detox/20.27.7-smoke.0/detox-20.27.7-smoke.0.aar +0 -0
- package/Detox-android/com/wix/detox/20.27.7-smoke.0/detox-20.27.7-smoke.0.aar.md5 +0 -1
- package/Detox-android/com/wix/detox/20.27.7-smoke.0/detox-20.27.7-smoke.0.aar.sha1 +0 -1
- package/Detox-android/com/wix/detox/20.27.7-smoke.0/detox-20.27.7-smoke.0.aar.sha256 +0 -1
- package/Detox-android/com/wix/detox/20.27.7-smoke.0/detox-20.27.7-smoke.0.aar.sha512 +0 -1
- package/Detox-android/com/wix/detox/20.27.7-smoke.0/detox-20.27.7-smoke.0.pom.md5 +0 -1
- package/Detox-android/com/wix/detox/20.27.7-smoke.0/detox-20.27.7-smoke.0.pom.sha1 +0 -1
- package/Detox-android/com/wix/detox/20.27.7-smoke.0/detox-20.27.7-smoke.0.pom.sha256 +0 -1
- package/Detox-android/com/wix/detox/20.27.7-smoke.0/detox-20.27.7-smoke.0.pom.sha512 +0 -1
- package/Detox-android/com/wix/detox-legacy/20.27.7-smoke.0/detox-legacy-20.27.7-smoke.0-sources.jar +0 -0
- package/Detox-android/com/wix/detox-legacy/20.27.7-smoke.0/detox-legacy-20.27.7-smoke.0-sources.jar.md5 +0 -1
- package/Detox-android/com/wix/detox-legacy/20.27.7-smoke.0/detox-legacy-20.27.7-smoke.0-sources.jar.sha1 +0 -1
- package/Detox-android/com/wix/detox-legacy/20.27.7-smoke.0/detox-legacy-20.27.7-smoke.0-sources.jar.sha256 +0 -1
- package/Detox-android/com/wix/detox-legacy/20.27.7-smoke.0/detox-legacy-20.27.7-smoke.0-sources.jar.sha512 +0 -1
- package/Detox-android/com/wix/detox-legacy/20.27.7-smoke.0/detox-legacy-20.27.7-smoke.0.aar +0 -0
- package/Detox-android/com/wix/detox-legacy/20.27.7-smoke.0/detox-legacy-20.27.7-smoke.0.aar.md5 +0 -1
- package/Detox-android/com/wix/detox-legacy/20.27.7-smoke.0/detox-legacy-20.27.7-smoke.0.aar.sha1 +0 -1
- package/Detox-android/com/wix/detox-legacy/20.27.7-smoke.0/detox-legacy-20.27.7-smoke.0.aar.sha256 +0 -1
- package/Detox-android/com/wix/detox-legacy/20.27.7-smoke.0/detox-legacy-20.27.7-smoke.0.aar.sha512 +0 -1
- package/Detox-android/com/wix/detox-legacy/20.27.7-smoke.0/detox-legacy-20.27.7-smoke.0.pom +0 -100
- package/Detox-android/com/wix/detox-legacy/20.27.7-smoke.0/detox-legacy-20.27.7-smoke.0.pom.md5 +0 -1
- package/Detox-android/com/wix/detox-legacy/20.27.7-smoke.0/detox-legacy-20.27.7-smoke.0.pom.sha1 +0 -1
- package/Detox-android/com/wix/detox-legacy/20.27.7-smoke.0/detox-legacy-20.27.7-smoke.0.pom.sha256 +0 -1
- package/Detox-android/com/wix/detox-legacy/20.27.7-smoke.0/detox-legacy-20.27.7-smoke.0.pom.sha512 +0 -1
- package/Detox-android/com/wix/detox-legacy/maven-metadata.xml +0 -13
- package/Detox-android/com/wix/detox-legacy/maven-metadata.xml.md5 +0 -1
- package/Detox-android/com/wix/detox-legacy/maven-metadata.xml.sha1 +0 -1
- package/Detox-android/com/wix/detox-legacy/maven-metadata.xml.sha256 +0 -1
- package/Detox-android/com/wix/detox-legacy/maven-metadata.xml.sha512 +0 -1
- package/android/detox/src/full/java/com/wix/detox/espresso/web/DetoxDriverAtoms.java +0 -642
- package/android/detox/src/full/java/com/wix/detox/reactnative/ReactNativeIdlingResources.kt +0 -229
- package/android/detox/src/full/java/com/wix/detox/reactnative/idlingresources/AnimatedModuleIdlingResource.java +0 -215
- package/android/detox/src/full/java/com/wix/detox/reactnative/idlingresources/BridgeIdlingResource.java +0 -94
- package/android/detox/src/full/java/com/wix/detox/reactnative/idlingresources/DetoxBaseIdlingResource.java +0 -29
- package/android/detox/src/full/java/com/wix/detox/reactnative/idlingresources/NetworkIdlingResource.java +0 -134
- package/android/detox/src/full/java/com/wix/detox/reactnative/idlingresources/timers/DelegatedIdleInterrogationStrategy.kt +0 -23
- package/android/detox/src/full/java/com/wix/detox/reactnative/idlingresources/timers/IdleInterrogationStrategy.kt +0 -16
- package/android/detox/src/full/java/com/wix/detox/reactnative/idlingresources/uimodule/RN66Workaround.kt +0 -71
- package/android/detox/src/testFull/java/com/wix/detox/reactnative/idlingresources/AsyncStorageIdlingResourceSpec.kt +0 -227
- package/android/detox/src/testFull/java/com/wix/detox/reactnative/idlingresources/timers/DelegatedIdleInterrogationStrategySpec.kt +0 -47
- package/android/detox/src/testFull/java/com/wix/detox/reactnative/idlingresources/timers/TimersIdlingResourceSpec.kt +0 -189
package/android/rninfo.gradle
CHANGED
@@ -1,11 +1,24 @@
|
|
1
1
|
import groovy.json.JsonSlurper
|
2
2
|
|
3
|
-
def
|
4
|
-
|
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 =
|
7
|
-
println("RNInfo: reading $
|
8
|
-
Map<String, Object> packageJSON = jsonSlurper.parse(new File(
|
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
|
-
|
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
|
-
|
27
|
-
println "[$project] RNInfo: detected React Native version: $rnVersion (major=$rnMajorVer)"
|
33
|
+
println "RNInfo: detected React Native version: $rnVersion (major=$rnMajorVer)"
|
28
34
|
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
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
|
+
]
|
package/android/settings.gradle
CHANGED
@@ -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 (
|
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.
|
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/
|
38
|
-
"@react-native/
|
39
|
-
"@react-native/
|
40
|
-
"@react-native/
|
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.
|
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.
|
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": "
|
122
|
+
"gitHead": "89a3f0efbac69cd37db886a53f712d51db8f857d"
|
120
123
|
}
|
package/scripts/postinstall.js
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
const { platform, env } = process;
|
2
2
|
|
3
|
-
const {
|
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
|
-
|
15
|
+
patchGradleByRNVersion();
|
package/scripts/updateGradle.js
CHANGED
@@ -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
|
21
|
-
|
22
|
-
|
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(
|
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
|
-
|
79
|
+
patchGradleByRNVersion: patchGradleByRNVersion
|
47
80
|
};
|
package/src/DetoxWorker.js
CHANGED
@@ -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 =
|
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
|
-
|
231
|
-
|
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)
|
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).
|
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);
|
@@ -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;
|