detox 20.9.1 → 20.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. package/.eslintignore +2 -0
  2. package/.eslintrc.js +1 -40
  3. package/Detox-android/com/wix/detox/{20.9.1/detox-20.9.1-javadoc.jar → 20.11.0/detox-20.11.0-javadoc.jar} +0 -0
  4. package/Detox-android/com/wix/detox/20.11.0/detox-20.11.0-javadoc.jar.md5 +1 -0
  5. package/Detox-android/com/wix/detox/20.11.0/detox-20.11.0-javadoc.jar.sha1 +1 -0
  6. package/Detox-android/com/wix/detox/20.11.0/detox-20.11.0-javadoc.jar.sha256 +1 -0
  7. package/Detox-android/com/wix/detox/20.11.0/detox-20.11.0-javadoc.jar.sha512 +1 -0
  8. package/Detox-android/com/wix/detox/{20.9.1/detox-20.9.1-sources.jar → 20.11.0/detox-20.11.0-sources.jar} +0 -0
  9. package/Detox-android/com/wix/detox/20.11.0/detox-20.11.0-sources.jar.md5 +1 -0
  10. package/Detox-android/com/wix/detox/20.11.0/detox-20.11.0-sources.jar.sha1 +1 -0
  11. package/Detox-android/com/wix/detox/20.11.0/detox-20.11.0-sources.jar.sha256 +1 -0
  12. package/Detox-android/com/wix/detox/20.11.0/detox-20.11.0-sources.jar.sha512 +1 -0
  13. package/Detox-android/com/wix/detox/20.11.0/detox-20.11.0.aar +0 -0
  14. package/Detox-android/com/wix/detox/20.11.0/detox-20.11.0.aar.md5 +1 -0
  15. package/Detox-android/com/wix/detox/20.11.0/detox-20.11.0.aar.sha1 +1 -0
  16. package/Detox-android/com/wix/detox/20.11.0/detox-20.11.0.aar.sha256 +1 -0
  17. package/Detox-android/com/wix/detox/20.11.0/detox-20.11.0.aar.sha512 +1 -0
  18. package/Detox-android/com/wix/detox/{20.9.1/detox-20.9.1.pom → 20.11.0/detox-20.11.0.pom} +1 -1
  19. package/Detox-android/com/wix/detox/20.11.0/detox-20.11.0.pom.md5 +1 -0
  20. package/Detox-android/com/wix/detox/20.11.0/detox-20.11.0.pom.sha1 +1 -0
  21. package/Detox-android/com/wix/detox/20.11.0/detox-20.11.0.pom.sha256 +1 -0
  22. package/Detox-android/com/wix/detox/20.11.0/detox-20.11.0.pom.sha512 +1 -0
  23. package/Detox-android/com/wix/detox/maven-metadata.xml +4 -4
  24. package/Detox-android/com/wix/detox/maven-metadata.xml.md5 +1 -1
  25. package/Detox-android/com/wix/detox/maven-metadata.xml.sha1 +1 -1
  26. package/Detox-android/com/wix/detox/maven-metadata.xml.sha256 +1 -1
  27. package/Detox-android/com/wix/detox/maven-metadata.xml.sha512 +1 -1
  28. package/Detox-ios-src.tbz +0 -0
  29. package/Detox-ios.tbz +0 -0
  30. package/android/build.gradle +20 -10
  31. package/android/detox/build.gradle +11 -4
  32. package/android/detox/src/full/java/com/wix/detox/espresso/DetoxMatcher.java +12 -12
  33. package/android/detox/src/full/java/com/wix/detox/espresso/common/SliderHelper.kt +2 -2
  34. package/android/detox/src/full/java/com/wix/detox/espresso/matcher/RegexMatcher.kt +56 -0
  35. package/android/detox/src/full/java/com/wix/detox/espresso/matcher/ViewMatchers.kt +16 -4
  36. package/android/detox/src/testFull/java/com/wix/detox/espresso/matcher/RegexMatcherTest.kt +52 -0
  37. package/android/gradle/wrapper/gradle-wrapper.properties +1 -1
  38. package/android/rninfo.gradle +25 -0
  39. package/android/settings.gradle +2 -1
  40. package/index.d.ts +10 -5
  41. package/local-cli/startCommand/AppStartCommand.js +4 -1
  42. package/package.json +14 -10
  43. package/src/android/espressoapi/DetoxMatcher.js +24 -8
  44. package/src/android/matchers/native.js +9 -4
  45. package/src/ios/expectTwo.js +8 -7
  46. package/src/utils/isRegExp.js +7 -0
  47. package/Detox-android/com/wix/detox/20.9.1/detox-20.9.1-javadoc.jar.md5 +0 -1
  48. package/Detox-android/com/wix/detox/20.9.1/detox-20.9.1-javadoc.jar.sha1 +0 -1
  49. package/Detox-android/com/wix/detox/20.9.1/detox-20.9.1-javadoc.jar.sha256 +0 -1
  50. package/Detox-android/com/wix/detox/20.9.1/detox-20.9.1-javadoc.jar.sha512 +0 -1
  51. package/Detox-android/com/wix/detox/20.9.1/detox-20.9.1-sources.jar.md5 +0 -1
  52. package/Detox-android/com/wix/detox/20.9.1/detox-20.9.1-sources.jar.sha1 +0 -1
  53. package/Detox-android/com/wix/detox/20.9.1/detox-20.9.1-sources.jar.sha256 +0 -1
  54. package/Detox-android/com/wix/detox/20.9.1/detox-20.9.1-sources.jar.sha512 +0 -1
  55. package/Detox-android/com/wix/detox/20.9.1/detox-20.9.1.aar +0 -0
  56. package/Detox-android/com/wix/detox/20.9.1/detox-20.9.1.aar.md5 +0 -1
  57. package/Detox-android/com/wix/detox/20.9.1/detox-20.9.1.aar.sha1 +0 -1
  58. package/Detox-android/com/wix/detox/20.9.1/detox-20.9.1.aar.sha256 +0 -1
  59. package/Detox-android/com/wix/detox/20.9.1/detox-20.9.1.aar.sha512 +0 -1
  60. package/Detox-android/com/wix/detox/20.9.1/detox-20.9.1.pom.md5 +0 -1
  61. package/Detox-android/com/wix/detox/20.9.1/detox-20.9.1.pom.sha1 +0 -1
  62. package/Detox-android/com/wix/detox/20.9.1/detox-20.9.1.pom.sha256 +0 -1
  63. package/Detox-android/com/wix/detox/20.9.1/detox-20.9.1.pom.sha512 +0 -1
package/.eslintignore CHANGED
@@ -3,3 +3,5 @@
3
3
  /ios
4
4
  /android
5
5
  /test
6
+ /allure-*
7
+ /artifacts
package/.eslintrc.js CHANGED
@@ -4,7 +4,7 @@ module.exports = {
4
4
  'eslint:recommended',
5
5
  'plugin:import/recommended',
6
6
  'plugin:node/recommended',
7
- 'plugin:unicorn/recommended'
7
+ 'plugin:ecmascript-compat/recommended'
8
8
  ],
9
9
  parser: '@typescript-eslint/parser',
10
10
  plugins: [
@@ -73,45 +73,6 @@ module.exports = {
73
73
  allowWarningComments: false,
74
74
  }
75
75
  ],
76
- // TODO: enable some of unicorn rules
77
- 'unicorn/better-regex': 'off',
78
- 'unicorn/catch-error-name': 'off',
79
- 'unicorn/consistent-destructuring': 'off',
80
- 'unicorn/consistent-function-scoping': 'off',
81
- 'unicorn/empty-brace-spaces': 'off',
82
- 'unicorn/error-message': 'off',
83
- 'unicorn/explicit-length-check': 'off',
84
- 'unicorn/filename-case': 'off',
85
- 'unicorn/import-style': 'off',
86
- 'unicorn/new-for-builtins': 'off',
87
- 'unicorn/no-abusive-eslint-disable': 'off',
88
- 'unicorn/no-array-callback-reference': 'off',
89
- 'unicorn/no-array-for-each': 'off',
90
- 'unicorn/no-array-reduce': 'off',
91
- 'unicorn/no-await-expression-member': 'off',
92
- 'unicorn/no-lonely-if': 'off',
93
- 'unicorn/no-nested-ternary': 'off',
94
- 'unicorn/no-new-array': 'off',
95
- 'unicorn/no-null': 'off',
96
- 'unicorn/no-object-as-default-parameter': 'off',
97
- 'unicorn/no-useless-undefined': 'off',
98
- 'unicorn/number-literal-case': 'off',
99
- 'unicorn/numeric-separators-style': 'off',
100
- 'unicorn/prefer-add-event-listener': 'off',
101
- 'unicorn/prefer-array-some': 'off',
102
- 'unicorn/prefer-array-flat': 'off',
103
- 'unicorn/prefer-includes': 'off',
104
- 'unicorn/prefer-module': 'off',
105
- 'unicorn/prefer-number-properties': 'off',
106
- 'unicorn/prefer-object-from-entries': 'off',
107
- 'unicorn/prefer-optional-catch-binding': 'off',
108
- 'unicorn/prefer-regexp-test': 'off',
109
- 'unicorn/prefer-spread': 'off',
110
- 'unicorn/prefer-string-slice': 'off',
111
- 'unicorn/prefer-string-starts-ends-with': 'off',
112
- 'unicorn/prefer-string-trim-start-end': 'off',
113
- 'unicorn/prefer-ternary': 'off',
114
- 'unicorn/prevent-abbreviations': 'off',
115
76
  },
116
77
 
117
78
  overrides: [
@@ -0,0 +1 @@
1
+ f0cf6ad0def43fbab5ba6fae7b4e5441
@@ -0,0 +1 @@
1
+ 48b29f67227c7b45844172ffdc0b25c7e5bd4f05
@@ -0,0 +1 @@
1
+ 0dbfb89e22024b3c3f45e0941a9fd3421d6a4f4041b76f8f54edd4259a9092a8
@@ -0,0 +1 @@
1
+ a1a2706b699d0c9217252c287a3d82b679bb206423ebbee820a261cd5925970426596f99a16fdcd8d37107185c3d5ec80628dd48309d0c31c89a00a37f7bf292
@@ -0,0 +1 @@
1
+ 3579eb8ea896facf3fd7e2be627cdc03
@@ -0,0 +1 @@
1
+ 1fb720e2b0f0da39406134aabcdafec756f9cbe5
@@ -0,0 +1 @@
1
+ f1c18496d23e09cffc96ef1b3bcbf349b1ae0a020ed5a6707b64bf22e30d9180
@@ -0,0 +1 @@
1
+ ebfcaef97fb6aa02bbda14d12a26eaac823620766c7e3b84eee7fa0115edb17463ea59ac689d07ffc97e84d2dbdbbd7db3014fdd976d0b2dd207c6278494f523
@@ -0,0 +1 @@
1
+ 518f621bfc037203c5ddd30c3f62c8ad
@@ -0,0 +1 @@
1
+ 65065fce4ddace8e95d235b60cf5a27f93e1162b
@@ -0,0 +1 @@
1
+ cd516e30305ad672e7ed0e8b4f77e992482c9a2979ce92d88a074dc45524cbe9
@@ -0,0 +1 @@
1
+ 00ff273c46cb32923f574fd00189459e9dff6d4782ac561f30583c9610427e270450783485aece4e9ebeb52c12bd3eec1560521d5a0535965dd278c7b44f3848
@@ -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.9.1</version>
6
+ <version>20.11.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
+ 72868177d685cfd9912ca8fe3c08da3a
@@ -0,0 +1 @@
1
+ ad0ed20f218ec25f055f5c668c9b6dcd875069bf
@@ -0,0 +1 @@
1
+ b1c48d9cf1df54b6cbe27c45a4c8834162b04a783601a3d4b030fd3ac12e723e
@@ -0,0 +1 @@
1
+ c68e22610d04ad949b65cffe6305e30bab93ca1a10d53d0cd3ded77dee8f64d7d14f1b250cbdde553a06a4fe5b50738af7ae363b5f7ad836f816f48ca845d189
@@ -3,11 +3,11 @@
3
3
  <groupId>com.wix</groupId>
4
4
  <artifactId>detox</artifactId>
5
5
  <versioning>
6
- <latest>20.9.1</latest>
7
- <release>20.9.1</release>
6
+ <latest>20.11.0</latest>
7
+ <release>20.11.0</release>
8
8
  <versions>
9
- <version>20.9.1</version>
9
+ <version>20.11.0</version>
10
10
  </versions>
11
- <lastUpdated>20230524200941</lastUpdated>
11
+ <lastUpdated>20230704204300</lastUpdated>
12
12
  </versioning>
13
13
  </metadata>
@@ -1 +1 @@
1
- 1d94c37700d4fbcc03221a27b3da67ed
1
+ e930da2d4686f5b8bbe2fe5638fab155
@@ -1 +1 @@
1
- 744c262d2fe22c39fafca012024e95592cdb583e
1
+ 7ec428486d1d978a16101aedc506e79483ccf63a
@@ -1 +1 @@
1
- 367b8367ac51f3b1268b86a694f294404cdb0dfe4786fb8267ea1c5f7e78e3cb
1
+ 469a65278a6ed368df597dfb505c150591e4ab587b190dbee2bb12c50b41a6ef
@@ -1 +1 @@
1
- 6de3736757b56504e83b488b7aabff2384a75d70ed303bac1ccb556615aef3adf491d9bd77287aa11a1cebfd88b0cda73e3d17c2498f53d04a65ba5b6678f882
1
+ dfc9bd1f96626dee848008639fc467b70d4653f57761c30cff16f41550d1f2a79dcd9aefd64c7313b8672c503e1a12dc360b1742faba6e2946878f6b2f8bbaac
package/Detox-ios-src.tbz CHANGED
Binary file
package/Detox-ios.tbz CHANGED
Binary file
@@ -1,11 +1,13 @@
1
1
  buildscript {
2
+ apply from: './rninfo.gradle'
3
+
2
4
  ext {
3
5
  isOfficialDetoxLib = true
4
- kotlinVersion = '1.6.10' // Aligned with RN .69's version bump
6
+ kotlinVersion = '1.8.22'
5
7
  dokkaVersion = '1.6.0'
6
- buildToolsVersion = '31.0.0'
7
- compileSdkVersion = 31
8
- targetSdkVersion = 31
8
+ buildToolsVersion = '33.0.0'
9
+ compileSdkVersion = 33
10
+ targetSdkVersion = 33
9
11
  minSdkVersion = 21
10
12
 
11
13
  if (System.properties['os.arch'] == "aarch64") {
@@ -19,11 +21,14 @@ buildscript {
19
21
  ext.detoxKotlinVersion = ext.kotlinVersion
20
22
 
21
23
  repositories {
22
- mavenCentral()
23
24
  google()
25
+ mavenCentral()
24
26
  }
25
27
  dependencies {
26
- classpath 'com.android.tools.build:gradle:7.2.1'
28
+ if (!rnInfo.isRN71OrNewer) {
29
+ classpath "com.facebook.react:react-native-gradle-plugin"
30
+ }
31
+ classpath 'com.android.tools.build:gradle:7.3.1'
27
32
  classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion"
28
33
  classpath "org.jetbrains.dokka:dokka-gradle-plugin:$dokkaVersion"
29
34
 
@@ -34,11 +39,16 @@ buildscript {
34
39
 
35
40
  allprojects {
36
41
  repositories {
37
- mavenLocal()
38
- mavenCentral()
39
42
  google()
40
- maven {
41
- url "$projectDir/../../node_modules/react-native/android"
43
+ mavenCentral()
44
+ mavenLocal()
45
+
46
+ // In RN's below 71, the native code comes from within node_modules/ rather
47
+ // than from maven-central.
48
+ if (!rnInfo.isRN71OrNewer) {
49
+ maven {
50
+ url "$projectDir/../../node_modules/react-native/android"
51
+ }
42
52
  }
43
53
  }
44
54
  }
@@ -1,5 +1,6 @@
1
1
  apply plugin: 'com.android.library'
2
2
  apply plugin: 'kotlin-android'
3
+ apply from: '../rninfo.gradle'
3
4
 
4
5
  def _kotlinMinVersion = '1.2.0'
5
6
  def _materialMinVersion = '1.2.1'
@@ -12,6 +13,13 @@ def _minSdkVersion = _ext.has('minSdkVersion') ? _ext.minSdkVersion : 21
12
13
  def _kotlinVersion = _ext.has('detoxKotlinVersion') ? _ext.detoxKotlinVersion : _kotlinMinVersion
13
14
  def _kotlinStdlib = _ext.has('detoxKotlinStdlib') ? _ext.detoxKotlinStdlib : 'kotlin-stdlib-jdk8'
14
15
 
16
+ // RN native code comes from either maven-central (in which case, need the *exact* version),
17
+ // or otherwise from node_modules/, where the version is already aligned, by definition.
18
+ // noinspection GradleDynamicVersion
19
+ def _rnNativeArtifact = rnInfo.isRN71OrHigher
20
+ ? "com.facebook.react:react-android:${rnInfo.version}"
21
+ : 'com.facebook.react:react-native:+'
22
+
15
23
  android {
16
24
  compileSdkVersion _compileSdkVersion
17
25
  buildToolsVersion _buildToolsVersion
@@ -78,8 +86,8 @@ android {
78
86
  // Fundamental deps.
79
87
  dependencies {
80
88
  implementation "org.jetbrains.kotlin:$_kotlinStdlib:$_kotlinMinVersion"
81
- //noinspection GradleDynamicVersion
82
- compileOnly 'com.facebook.react:react-native:+'
89
+
90
+ compileOnly "${_rnNativeArtifact}"
83
91
  }
84
92
 
85
93
  // androidx.test deps.
@@ -131,8 +139,7 @@ dependencies {
131
139
 
132
140
  // Unit-testing deps.
133
141
  dependencies {
134
- //noinspection GradleDynamicVersion
135
- testImplementation 'com.facebook.react:react-native:+'
142
+ testImplementation "${_rnNativeArtifact}"
136
143
  testImplementation 'org.json:json:20140107'
137
144
 
138
145
  // https://github.com/spekframework/spek/issues/232#issuecomment-610732158
@@ -14,10 +14,10 @@ import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
14
14
  import static androidx.test.espresso.matcher.ViewMatchers.isDisplayingAtLeast;
15
15
  import static androidx.test.espresso.matcher.ViewMatchers.isFocused;
16
16
  import static androidx.test.espresso.matcher.ViewMatchers.isNotChecked;
17
- import static androidx.test.espresso.matcher.ViewMatchers.withContentDescription;
18
17
  import static androidx.test.espresso.matcher.ViewMatchers.withEffectiveVisibility;
19
- import static androidx.test.espresso.matcher.ViewMatchers.withTagValue;
20
- import static androidx.test.espresso.matcher.ViewMatchers.withText;
18
+ import static com.wix.detox.espresso.matcher.ViewMatchers.withTagValue;
19
+ import static com.wix.detox.espresso.matcher.ViewMatchers.withContentDescription;
20
+ import static com.wix.detox.espresso.matcher.ViewMatchers.withText;
21
21
  import static com.wix.detox.espresso.matcher.ViewMatchers.isMatchingAtIndex;
22
22
  import static com.wix.detox.espresso.matcher.ViewMatchers.isOfClassName;
23
23
  import static com.wix.detox.espresso.matcher.ViewMatchers.toHaveSliderPosition;
@@ -40,25 +40,25 @@ public class DetoxMatcher {
40
40
  // static class
41
41
  }
42
42
 
43
- public static Matcher<View> matcherForText(String text) {
43
+ public static Matcher<View> matcherForText(String text, boolean isRegex) {
44
44
  // return anyOf(withText(text), withContentDescription(text));
45
- return allOf(withText(text), withEffectiveVisibility(Visibility.VISIBLE));
45
+ return allOf(withText(text, isRegex), withEffectiveVisibility(Visibility.VISIBLE));
46
46
  }
47
47
 
48
- public static Matcher<View> matcherForAccessibilityLabel(String label) {
49
- return allOf(withAccessibilityLabel(label), withEffectiveVisibility(Visibility.VISIBLE));
48
+ public static Matcher<View> matcherForAccessibilityLabel(String label, boolean isRegex) {
49
+ return allOf(withAccessibilityLabel(label, isRegex), withEffectiveVisibility(Visibility.VISIBLE));
50
50
  }
51
51
 
52
- public static Matcher<View> matcherForShallowAccessibilityLabel(String label) {
53
- return allOf(withShallowAccessibilityLabel(label), withEffectiveVisibility(Visibility.VISIBLE));
52
+ public static Matcher<View> matcherForShallowAccessibilityLabel(String label, boolean isRegex) {
53
+ return allOf(withShallowAccessibilityLabel(label, isRegex), withEffectiveVisibility(Visibility.VISIBLE));
54
54
  }
55
55
 
56
56
  public static Matcher<View> matcherForContentDescription(String contentDescription) {
57
- return allOf(withContentDescription(contentDescription), withEffectiveVisibility(Visibility.VISIBLE));
57
+ return allOf(withContentDescription(contentDescription, false), withEffectiveVisibility(Visibility.VISIBLE));
58
58
  }
59
59
 
60
- public static Matcher<View> matcherForTestId(String testId) {
61
- return allOf(withTagValue(is((Object) testId)), withEffectiveVisibility(Visibility.VISIBLE));
60
+ public static Matcher<View> matcherForTestId(String testId, boolean isRegex) {
61
+ return allOf(withTagValue(testId, isRegex), withEffectiveVisibility(Visibility.VISIBLE));
62
62
  }
63
63
 
64
64
  public static Matcher<View> matcherForToggleable(boolean value) {
@@ -4,9 +4,9 @@ import android.view.View
4
4
  import androidx.appcompat.widget.AppCompatSeekBar
5
5
  import com.facebook.react.bridge.JavaOnlyMap
6
6
  import com.facebook.react.uimanager.ReactStylesDiffMap
7
- import com.facebook.react.views.slider.ReactSlider
8
7
  import com.wix.detox.common.DetoxErrors.DetoxIllegalStateException
9
8
  import com.wix.detox.espresso.action.common.ReflectUtils
9
+ import com.facebook.react.views.slider.ReactSlider
10
10
  import org.joor.Reflect
11
11
 
12
12
  private const val CLASS_REACT_SLIDER_LEGACY = "com.facebook.react.views.slider.ReactSlider"
@@ -58,7 +58,7 @@ abstract class SliderHelper(protected val slider: AppCompatSeekBar) {
58
58
  }
59
59
  }
60
60
 
61
- private class LegacySliderHelper(slider: AppCompatSeekBar): SliderHelper(slider) {
61
+ private class LegacySliderHelper(slider: ReactSlider): SliderHelper(slider) {
62
62
  override fun setProgressJS(valueJS: Double) {
63
63
  val reactSliderManager = com.facebook.react.views.slider.ReactSliderManager()
64
64
  reactSliderManager.updateProperties(slider as ReactSlider, buildStyles("value", valueJS))
@@ -0,0 +1,56 @@
1
+ package com.wix.detox.espresso.matcher
2
+
3
+ import org.hamcrest.Description
4
+ import org.hamcrest.TypeSafeMatcher
5
+
6
+ class RegexMatcher<T>(private val jsRegex: String) : TypeSafeMatcher<T>() {
7
+ override fun matchesSafely(item: T): Boolean {
8
+ val stringItem = item.toString()
9
+ return stringItem.matchesJSRegex(jsRegex)
10
+ }
11
+
12
+ override fun describeTo(description: Description) {
13
+ description.appendText("should match the pattern: $jsRegex")
14
+ }
15
+ }
16
+
17
+ // Returns whether the whole string matches the given `jsRegex`.
18
+ // JS flags has the format of `/<pattern>/<flags>`.
19
+ // Flags can be either:
20
+ // - i: With this flag the search is case-insensitive: no difference between A and a (see the example below).
21
+ // - s: Enables “dotall” mode, that allows a dot . to match newline character \n (covered in the chapter Character classes).
22
+ // - m: Multiline mode (covered in the chapter Multiline mode of anchors ^ $, flag "m").
23
+ // Other flags (e.g. g,u,s) are not supported as they do not have equivalents in Kotlin.
24
+ //
25
+ // - See: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions
26
+ fun String.matchesJSRegex(jsRegex: String): Boolean {
27
+ val flagsChars = getRegexFlags(jsRegex)
28
+ val options = getRegexOptions(flagsChars)
29
+ val pattern = getRegexPattern(jsRegex)
30
+ return Regex(pattern, options).matches(this)
31
+ }
32
+
33
+ private fun getRegexFlags(jsRegex: String): CharSequence {
34
+ return jsRegex.substringAfterLast("/")
35
+ }
36
+
37
+ private fun getRegexPattern(jsRegex: String): String {
38
+ val pattern = jsRegex.substringAfter("/")
39
+ return pattern.substringBeforeLast("/")
40
+ }
41
+
42
+ private fun getRegexOptions(flagsChars: CharSequence): MutableSet<RegexOption> {
43
+ val options = mutableSetOf<RegexOption>()
44
+
45
+ if (flagsChars.contains('i', ignoreCase = true)) {
46
+ options.add(RegexOption.IGNORE_CASE)
47
+ }
48
+ if (flagsChars.contains('s', ignoreCase = true)) {
49
+ options.add(RegexOption.DOT_MATCHES_ALL)
50
+ }
51
+ if (flagsChars.contains('m', ignoreCase = true)) {
52
+ options.add(RegexOption.MULTILINE)
53
+ }
54
+
55
+ return options
56
+ }
@@ -11,16 +11,28 @@ import com.wix.detox.espresso.common.SliderHelper
11
11
  import org.hamcrest.*
12
12
  import org.hamcrest.Matchers.*
13
13
  import kotlin.math.abs
14
+ import org.hamcrest.CoreMatchers.`is`
14
15
 
15
16
  /*
16
17
  * An extension of [androidx.test.espresso.matcher.ViewMatchers].
17
18
  */
19
+ fun <T> getRelevantMatcher(value: T, isRegex: Boolean): Matcher<T> =
20
+ if (isRegex) RegexMatcher(value.toString()) else `is`(value)
18
21
 
19
- fun withAccessibilityLabel(text: String) =
20
- WithAccessibilityLabelMatcher(`is`(text))
22
+ fun withAccessibilityLabel(text: String, isRegex: Boolean): Matcher<View> =
23
+ WithAccessibilityLabelMatcher(getRelevantMatcher(text, isRegex))
21
24
 
22
- fun withShallowAccessibilityLabel(label: String): Matcher<View>
23
- = anyOf(ViewMatchers.withContentDescription(label), ViewMatchers.withText(label))
25
+ fun withShallowAccessibilityLabel(label: String, isRegex: Boolean): Matcher<View> =
26
+ anyOf(withContentDescription(label, isRegex), withText(label, isRegex))
27
+
28
+ fun withText(text: String, isRegex: Boolean): Matcher<View> =
29
+ ViewMatchers.withText(getRelevantMatcher(text, isRegex))
30
+
31
+ fun withContentDescription(label: String, isRegex: Boolean): Matcher<View> =
32
+ ViewMatchers.withContentDescription(getRelevantMatcher(label, isRegex))
33
+
34
+ fun withTagValue(testId: String, isRegex: Boolean): Matcher<View> =
35
+ ViewMatchers.withTagValue(getRelevantMatcher<Any>(testId, isRegex))
24
36
 
25
37
  fun isOfClassName(className: String): Matcher<View> {
26
38
  try {
@@ -0,0 +1,52 @@
1
+ package com.wix.detox.espresso.matcher
2
+
3
+ import org.junit.Test
4
+ import kotlin.test.assertFalse
5
+ import kotlin.test.assertTrue
6
+ import org.junit.runner.RunWith
7
+ import org.robolectric.RobolectricTestRunner
8
+
9
+ @RunWith(RobolectricTestRunner::class)
10
+ class RegexMatcherTest {
11
+ @Test
12
+ fun `should work with string matching regex`() {
13
+ val input = "Hello, world!"
14
+ val regex = "/[A-Z][a-z]+, world!/"
15
+ assertTrue(input.matchesJSRegex(regex))
16
+ }
17
+
18
+ @Test
19
+ fun `should work with string not matching regex`() {
20
+ val input = "Hello, world!"
21
+ val regex = "/[A-Z]+, world!/"
22
+ assertFalse(input.matchesJSRegex(regex))
23
+ }
24
+
25
+ @Test
26
+ fun `should work with the 'i' flag`() {
27
+ val input = "Hello, world!"
28
+ val regex = "/[A-Z]+, woRlD!/i"
29
+ assertTrue(input.matchesJSRegex(regex))
30
+ }
31
+
32
+ @Test
33
+ fun `should work with the 's' flag`() {
34
+ val input = "Hello,\nworld!"
35
+ val regex = "/Hello,\\sworld!/s"
36
+ assertTrue(input.matchesJSRegex(regex))
37
+ }
38
+
39
+ @Test
40
+ fun `should work with the 'm' flag`() {
41
+ val input = "Hello,\nworld!"
42
+ val regex = "/^Hello,\\s.*!$/m"
43
+ assertTrue(input.matchesJSRegex(regex))
44
+ }
45
+
46
+ @Test
47
+ fun `should work with multiple flags, ignore casing`() {
48
+ val input = "Hello,\nworld!"
49
+ val regex = "/^heLLo,\\swOrld!/ISM"
50
+ assertTrue(input.matchesJSRegex(regex))
51
+ }
52
+ }
@@ -3,5 +3,5 @@ distributionBase=GRADLE_USER_HOME
3
3
  distributionPath=wrapper/dists
4
4
  zipStoreBase=GRADLE_USER_HOME
5
5
  zipStorePath=wrapper/dists
6
- distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-all.zip
6
+ distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.1-all.zip
7
7
 
@@ -0,0 +1,25 @@
1
+ import groovy.json.JsonSlurper
2
+
3
+ def rnVersion = getRNVersion()
4
+ def rnMajorVer = getMajorVersion(rnVersion)
5
+ println "[$project] RNInfo: detected React Native version: $rnVersion (major=$rnMajorVer)"
6
+
7
+ project.ext.rnInfo = [
8
+ version: rnVersion,
9
+ majorVersion: rnMajorVer,
10
+ isRN69OrHigher: rnMajorVer >= 69,
11
+ isRN70OrHigher: rnMajorVer >= 70,
12
+ isRN71OrHigher: rnMajorVer >= 71,
13
+ ]
14
+
15
+ private static def getRNVersion() {
16
+ def jsonSlurper = new JsonSlurper()
17
+ Map<String, Object> packageJSON = jsonSlurper.parse(new File('../node_modules/react-native/package.json'))
18
+ String rnVersion = packageJSON.get('version')
19
+ return rnVersion
20
+ }
21
+
22
+ private static def getMajorVersion(semanticVersion) {
23
+ Integer rnVersionMajor = semanticVersion.split('\\.')[1].toInteger()
24
+ return rnVersionMajor
25
+ }
@@ -1 +1,2 @@
1
- include ':detox'
1
+ include ':detox'
2
+ includeBuild('../node_modules/react-native-gradle-plugin')
package/index.d.ts CHANGED
@@ -971,20 +971,25 @@ declare global {
971
971
  * <TouchableOpacity testID={'tap_me'}>
972
972
  * // Then match with by.id:
973
973
  * await element(by.id('tap_me'));
974
+ * await element(by.id(/^tap_[a-z]+$/));
974
975
  */
975
- id(id: string): NativeMatcher;
976
+ id(id: string | RegExp): NativeMatcher;
976
977
 
977
978
  /**
978
979
  * Find an element by text, useful for text fields, buttons.
979
- * @example await element(by.text('Tap Me'));
980
+ * @example
981
+ * await element(by.text('Tap Me'));
982
+ * await element(by.text(/^Tap .*$/));
980
983
  */
981
- text(text: string): NativeMatcher;
984
+ text(text: string | RegExp): NativeMatcher;
982
985
 
983
986
  /**
984
987
  * Find an element by accessibilityLabel on iOS, or by contentDescription on Android.
985
- * @example await element(by.label('Welcome'));
988
+ * @example
989
+ * await element(by.label('Welcome'));
990
+ * await element(by.label(/[a-z]+/i));
986
991
  */
987
- label(label: string): NativeMatcher;
992
+ label(label: string | RegExp): NativeMatcher;
988
993
 
989
994
  /**
990
995
  * Find an element by native view type.
@@ -36,7 +36,10 @@ class AppStartCommand {
36
36
  }
37
37
  };
38
38
 
39
- this._cpHandle = execa.command(cmd, { stdio: 'inherit', shell: true });
39
+ this._cpHandle = execa.command(cmd, {
40
+ stdio: ['ignore', 'inherit', 'inherit'],
41
+ shell: true
42
+ });
40
43
  this._cpHandle.on('error', onError);
41
44
  this._cpHandle.on('exit', (code, signal) => {
42
45
  const reason = code == null ? `signal ${signal}` : `code ${code}`;
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.9.1",
4
+ "version": "20.11.0",
5
5
  "bin": {
6
6
  "detox": "local-cli/cli.js"
7
7
  },
@@ -41,19 +41,20 @@
41
41
  "@types/node": "^14.18.33",
42
42
  "@types/node-ipc": "^9.2.0",
43
43
  "@types/ws": "^7.4.0",
44
- "@typescript-eslint/eslint-plugin": "^5.4.0",
45
- "@typescript-eslint/parser": "^5.4.0",
44
+ "@typescript-eslint/eslint-plugin": "^5.59.8",
45
+ "@typescript-eslint/parser": "^5.59.8",
46
46
  "cross-env": "^7.0.3",
47
- "eslint": "^8.3.0",
48
- "eslint-plugin-import": "^2.23.3",
49
- "eslint-plugin-no-only-tests": "^2.6.0",
47
+ "eslint": "^8.41.0",
48
+ "eslint-plugin-ecmascript-compat": "^3.0.0",
49
+ "eslint-plugin-import": "^2.27.5",
50
+ "eslint-plugin-no-only-tests": "^3.1.0",
50
51
  "eslint-plugin-node": "^11.1.0",
51
- "eslint-plugin-unicorn": "^39.0.0",
52
+ "eslint-plugin-unicorn": "^47.0.0",
52
53
  "jest": "^28.1.3",
53
54
  "jest-allure2-reporter": "^1.2.1",
54
55
  "mockdate": "^2.0.1",
55
- "prettier": "1.7.0",
56
- "react-native": "0.70.7",
56
+ "prettier": "^2.4.1",
57
+ "react-native": "0.71.10",
57
58
  "react-native-codegen": "^0.0.8",
58
59
  "typescript": "^4.5.2",
59
60
  "wtfnode": "^0.9.1"
@@ -202,5 +203,8 @@
202
203
  }
203
204
  }
204
205
  },
205
- "gitHead": "354ee9919959b322d9907570aa57d0be65bdf8fc"
206
+ "browserslist": [
207
+ "node 14"
208
+ ],
209
+ "gitHead": "e132a02242f1a4ebcf1775ba4ceb782d5b1a855c"
206
210
  }
@@ -14,39 +14,51 @@ function sanitize_matcher(matcher) {
14
14
  return originalMatcher.type ? originalMatcher.value : originalMatcher;
15
15
  }
16
16
  class DetoxMatcher {
17
- static matcherForText(text) {
17
+ static matcherForText(text, isRegex) {
18
18
  if (typeof text !== "string") throw new Error("text should be a string, but got " + (text + (" (" + (typeof text + ")"))));
19
+ if (typeof isRegex !== "boolean") throw new Error("isRegex should be a boolean, but got " + (isRegex + (" (" + (typeof isRegex + ")"))));
19
20
  return {
20
21
  target: {
21
22
  type: "Class",
22
23
  value: "com.wix.detox.espresso.DetoxMatcher"
23
24
  },
24
25
  method: "matcherForText",
25
- args: [text]
26
+ args: [text, {
27
+ type: "boolean",
28
+ value: isRegex
29
+ }]
26
30
  };
27
31
  }
28
32
 
29
- static matcherForAccessibilityLabel(label) {
33
+ static matcherForAccessibilityLabel(label, isRegex) {
30
34
  if (typeof label !== "string") throw new Error("label should be a string, but got " + (label + (" (" + (typeof label + ")"))));
35
+ if (typeof isRegex !== "boolean") throw new Error("isRegex should be a boolean, but got " + (isRegex + (" (" + (typeof isRegex + ")"))));
31
36
  return {
32
37
  target: {
33
38
  type: "Class",
34
39
  value: "com.wix.detox.espresso.DetoxMatcher"
35
40
  },
36
41
  method: "matcherForAccessibilityLabel",
37
- args: [label]
42
+ args: [label, {
43
+ type: "boolean",
44
+ value: isRegex
45
+ }]
38
46
  };
39
47
  }
40
48
 
41
- static matcherForShallowAccessibilityLabel(label) {
49
+ static matcherForShallowAccessibilityLabel(label, isRegex) {
42
50
  if (typeof label !== "string") throw new Error("label should be a string, but got " + (label + (" (" + (typeof label + ")"))));
51
+ if (typeof isRegex !== "boolean") throw new Error("isRegex should be a boolean, but got " + (isRegex + (" (" + (typeof isRegex + ")"))));
43
52
  return {
44
53
  target: {
45
54
  type: "Class",
46
55
  value: "com.wix.detox.espresso.DetoxMatcher"
47
56
  },
48
57
  method: "matcherForShallowAccessibilityLabel",
49
- args: [label]
58
+ args: [label, {
59
+ type: "boolean",
60
+ value: isRegex
61
+ }]
50
62
  };
51
63
  }
52
64
 
@@ -62,15 +74,19 @@ class DetoxMatcher {
62
74
  };
63
75
  }
64
76
 
65
- static matcherForTestId(testId) {
77
+ static matcherForTestId(testId, isRegex) {
66
78
  if (typeof testId !== "string") throw new Error("testId should be a string, but got " + (testId + (" (" + (typeof testId + ")"))));
79
+ if (typeof isRegex !== "boolean") throw new Error("isRegex should be a boolean, but got " + (isRegex + (" (" + (typeof isRegex + ")"))));
67
80
  return {
68
81
  target: {
69
82
  type: "Class",
70
83
  value: "com.wix.detox.espresso.DetoxMatcher"
71
84
  },
72
85
  method: "matcherForTestId",
73
- args: [testId]
86
+ args: [testId, {
87
+ type: "boolean",
88
+ value: isRegex
89
+ }]
74
90
  };
75
91
  }
76
92
 
@@ -1,26 +1,30 @@
1
1
  const DetoxRuntimeError = require('../../errors/DetoxRuntimeError');
2
2
  const invoke = require('../../invoke');
3
+ const { isRegExp } = require('../../utils/isRegExp');
3
4
  const { NativeMatcher } = require('../core/NativeMatcher');
4
5
  const DetoxMatcherApi = require('../espressoapi/DetoxMatcher');
5
6
 
6
7
  class LabelMatcher extends NativeMatcher {
7
8
  constructor(value) {
8
9
  super();
9
- this._call = invoke.callDirectly(DetoxMatcherApi.matcherForAccessibilityLabel(value));
10
+ const isRegex = isRegExp(value);
11
+ this._call = invoke.callDirectly(DetoxMatcherApi.matcherForAccessibilityLabel(isRegex ? value.toString() : value, isRegex));
10
12
  }
11
13
  }
12
14
 
13
15
  class ShallowLabelMatcher extends NativeMatcher {
14
16
  constructor(value) {
15
17
  super();
16
- this._call = invoke.callDirectly(DetoxMatcherApi.matcherForShallowAccessibilityLabel(value));
18
+ const isRegex = isRegExp(value);
19
+ this._call = invoke.callDirectly(DetoxMatcherApi.matcherForShallowAccessibilityLabel(isRegex ? value.toString() : value, isRegex));
17
20
  }
18
21
  }
19
22
 
20
23
  class IdMatcher extends NativeMatcher {
21
24
  constructor(value) {
22
25
  super();
23
- this._call = invoke.callDirectly(DetoxMatcherApi.matcherForTestId(value));
26
+ const isRegex = isRegExp(value);
27
+ this._call = invoke.callDirectly(DetoxMatcherApi.matcherForTestId(isRegex ? value.toString() : value, isRegex));
24
28
  }
25
29
  }
26
30
 
@@ -53,7 +57,8 @@ class ExistsMatcher extends NativeMatcher {
53
57
  class TextMatcher extends NativeMatcher {
54
58
  constructor(value) {
55
59
  super();
56
- this._call = invoke.callDirectly(DetoxMatcherApi.matcherForText(value));
60
+ const isRegex = isRegExp(value);
61
+ this._call = invoke.callDirectly(DetoxMatcherApi.matcherForText(isRegex ? value.toString() : value, isRegex));
57
62
  }
58
63
  }
59
64
 
@@ -9,6 +9,7 @@ const tempfile = require('tempfile');
9
9
  const { assertEnum, assertNormalized } = require('../utils/assertArgument');
10
10
  const { removeMilliseconds } = require('../utils/dateUtils');
11
11
  const { actionDescription, expectDescription } = require('../utils/invocationTraceDescriptions');
12
+ const { isRegExp } = require('../utils/isRegExp');
12
13
  const log = require('../utils/logger').child({ cat: 'ws-client, ws' });
13
14
  const traceInvocationCall = require('../utils/traceInvocationCall').bind(null, log);
14
15
 
@@ -231,7 +232,7 @@ class Element {
231
232
 
232
233
  performAccessibilityAction(actionName) {
233
234
  if (typeof actionName !== 'string') throw new Error('actionName should be a string, but got ' + (actionName + (' (' + (typeof actionName + ')'))));
234
-
235
+
235
236
  const traceDescription = actionDescription.performAccessibilityAction(actionName);
236
237
  return this.withAction('accessibilityAction', traceDescription, actionName);
237
238
  }
@@ -409,14 +410,14 @@ class Matcher {
409
410
  }
410
411
 
411
412
  label(label) {
412
- if (typeof label !== 'string') throw new Error('label should be a string, but got ' + (label + (' (' + (typeof label + ')'))));
413
- this.predicate = { type: 'label', value: label };
413
+ if (typeof label !== 'string' && !isRegExp(label)) throw new Error('label should be a string or regex, but got ' + (label + (' (' + (typeof label + ')'))));
414
+ this.predicate = { type: 'label', value: label.toString(), isRegex: isRegExp(label) };
414
415
  return this;
415
416
  }
416
417
 
417
418
  id(id) {
418
- if (typeof id !== 'string') throw new Error('id should be a string, but got ' + (id + (' (' + (typeof id + ')'))));
419
- this.predicate = { type: 'id', value: id };
419
+ if (typeof id !== 'string' && !isRegExp(id)) throw new Error('id should be a string or regex, but got ' + (id + (' (' + (typeof id + ')'))));
420
+ this.predicate = { type: 'id', value: id.toString(), isRegex: isRegExp(id) };
420
421
  return this;
421
422
  }
422
423
 
@@ -439,8 +440,8 @@ class Matcher {
439
440
  }
440
441
 
441
442
  text(text) {
442
- if (typeof text !== 'string') throw new Error('text should be a string, but got ' + (text + (' (' + (typeof text + ')'))));
443
- this.predicate = { type: 'text', value: text };
443
+ if (typeof text !== 'string' && !isRegExp(text)) throw new Error(`text should be a string or regex, but got ` + (text + (' (' + (typeof text + ')'))));
444
+ this.predicate = { type: 'text', value: text.toString(), isRegex: isRegExp(text) };
444
445
  return this;
445
446
  }
446
447
 
@@ -0,0 +1,7 @@
1
+ function isRegExp(obj) {
2
+ return Object.prototype.toString.call(obj) === '[object RegExp]';
3
+ }
4
+
5
+ module.exports = {
6
+ isRegExp,
7
+ };
@@ -1 +0,0 @@
1
- e135691ef3c16000236576219ead8095
@@ -1 +0,0 @@
1
- 2ae996dffa140d6ef33003e472c5396bcf1fed97
@@ -1 +0,0 @@
1
- 489299047e3a1a1a10102b834874b119d5373263c8df9d010dd3696af0ca3cb5
@@ -1 +0,0 @@
1
- e5d017cb7ea0f9707dca69c80012edcd1320436c72a1bf922bcddf7526c885ba1a16e3bcbfc32122235ac2f122f2d40890f45b46d69eafb6d4bd612097a634c6
@@ -1 +0,0 @@
1
- 75020f38aa6029c7692b7a014159143f
@@ -1 +0,0 @@
1
- d0695ef9339d69af09a14e77c495339e5ff00700
@@ -1 +0,0 @@
1
- de56a9ce5199b6954621b9ab71b065882f50a50f7e723f78540bed54a17225fb
@@ -1 +0,0 @@
1
- 80684cd2db41f4c70e9a5795578b7eb7f528b39af836deec99a08e3ad38cf49c1e37bec6b98c8aedf0fdb03a6ca881b136149121639016dffe402f666152ca7b
@@ -1 +0,0 @@
1
- e89d3204dcda439e4deb0df61603394a
@@ -1 +0,0 @@
1
- eda5c226e954731b3dbd16becbab72f66888d5ce
@@ -1 +0,0 @@
1
- 4da9ab41881efc27c7268bff2bfbb7092d9dbdf6de8f43787b4df649f0291bff
@@ -1 +0,0 @@
1
- c63bf901eb0327ca3791b419f99dd67060a9b48a816da5c71e7e660e60fed607347ef8f76710969e9497054cc4f99add8db19f47b7e780ee3e6413df6d75e195
@@ -1 +0,0 @@
1
- c40ce1eb663c728fcb4997417bd2bad2
@@ -1 +0,0 @@
1
- 32a60d3d786cd018febcd30ae1a09a30772e1112
@@ -1 +0,0 @@
1
- de63c952930025cf608b8079311242c75647b0daa0f8ed18d4a5bf10f013f9c3
@@ -1 +0,0 @@
1
- 5a7090284861016e6589c5ddb404f8075ee1926ebfec73ce993e0f41048163e3b27876fe10c43f9c3f9dc80c842841eeb0d1b842737b87afba29334369e839c7