detox 21.0.0-rc.3 → 21.0.0-rc.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (72) hide show
  1. package/.eslintignore +2 -0
  2. package/.eslintrc.js +1 -40
  3. package/Detox-android/com/wix/detox/{21.0.0-rc.3/detox-21.0.0-rc.3-javadoc.jar → 21.0.0-rc.4/detox-21.0.0-rc.4-javadoc.jar} +0 -0
  4. package/Detox-android/com/wix/detox/21.0.0-rc.4/detox-21.0.0-rc.4-javadoc.jar.md5 +1 -0
  5. package/Detox-android/com/wix/detox/21.0.0-rc.4/detox-21.0.0-rc.4-javadoc.jar.sha1 +1 -0
  6. package/Detox-android/com/wix/detox/21.0.0-rc.4/detox-21.0.0-rc.4-javadoc.jar.sha256 +1 -0
  7. package/Detox-android/com/wix/detox/21.0.0-rc.4/detox-21.0.0-rc.4-javadoc.jar.sha512 +1 -0
  8. package/Detox-android/com/wix/detox/{21.0.0-rc.3/detox-21.0.0-rc.3-sources.jar → 21.0.0-rc.4/detox-21.0.0-rc.4-sources.jar} +0 -0
  9. package/Detox-android/com/wix/detox/21.0.0-rc.4/detox-21.0.0-rc.4-sources.jar.md5 +1 -0
  10. package/Detox-android/com/wix/detox/21.0.0-rc.4/detox-21.0.0-rc.4-sources.jar.sha1 +1 -0
  11. package/Detox-android/com/wix/detox/21.0.0-rc.4/detox-21.0.0-rc.4-sources.jar.sha256 +1 -0
  12. package/Detox-android/com/wix/detox/21.0.0-rc.4/detox-21.0.0-rc.4-sources.jar.sha512 +1 -0
  13. package/Detox-android/com/wix/detox/21.0.0-rc.4/detox-21.0.0-rc.4.aar +0 -0
  14. package/Detox-android/com/wix/detox/21.0.0-rc.4/detox-21.0.0-rc.4.aar.md5 +1 -0
  15. package/Detox-android/com/wix/detox/21.0.0-rc.4/detox-21.0.0-rc.4.aar.sha1 +1 -0
  16. package/Detox-android/com/wix/detox/21.0.0-rc.4/detox-21.0.0-rc.4.aar.sha256 +1 -0
  17. package/Detox-android/com/wix/detox/21.0.0-rc.4/detox-21.0.0-rc.4.aar.sha512 +1 -0
  18. package/Detox-android/com/wix/detox/{21.0.0-rc.3/detox-21.0.0-rc.3.pom → 21.0.0-rc.4/detox-21.0.0-rc.4.pom} +1 -1
  19. package/Detox-android/com/wix/detox/21.0.0-rc.4/detox-21.0.0-rc.4.pom.md5 +1 -0
  20. package/Detox-android/com/wix/detox/21.0.0-rc.4/detox-21.0.0-rc.4.pom.sha1 +1 -0
  21. package/Detox-android/com/wix/detox/21.0.0-rc.4/detox-21.0.0-rc.4.pom.sha256 +1 -0
  22. package/Detox-android/com/wix/detox/21.0.0-rc.4/detox-21.0.0-rc.4.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-framework.tbz +0 -0
  29. package/Detox-ios-src.tbz +0 -0
  30. package/Detox-ios-xcuitest.tbz +0 -0
  31. package/android/build.gradle +20 -10
  32. package/android/detox/build.gradle +11 -4
  33. package/android/detox/src/full/java/com/wix/detox/espresso/DetoxMatcher.java +12 -12
  34. package/android/detox/src/full/java/com/wix/detox/espresso/common/SliderHelper.kt +2 -2
  35. package/android/detox/src/full/java/com/wix/detox/espresso/matcher/RegexMatcher.kt +56 -0
  36. package/android/detox/src/full/java/com/wix/detox/espresso/matcher/ViewMatchers.kt +16 -4
  37. package/android/detox/src/testFull/java/com/wix/detox/espresso/matcher/RegexMatcherTest.kt +52 -0
  38. package/android/gradle/wrapper/gradle-wrapper.properties +1 -1
  39. package/android/rninfo.gradle +25 -0
  40. package/android/settings.gradle +2 -1
  41. package/index.d.ts +40 -6
  42. package/local-cli/startCommand/AppStartCommand.js +4 -1
  43. package/package.json +14 -10
  44. package/src/android/espressoapi/DetoxMatcher.js +24 -8
  45. package/src/android/matchers/index.js +3 -0
  46. package/src/android/matchers/native.js +9 -4
  47. package/src/android/matchers/web.js +18 -1
  48. package/src/devices/runtime/drivers/ios/XCUITestUtils.js +3 -4
  49. package/src/invoke.js +0 -2
  50. package/src/ios/expectTwo.js +28 -11
  51. package/src/ios/web.js +280 -0
  52. package/src/realms/DetoxPrimaryContext.js +1 -2
  53. package/src/utils/invocationTraceDescriptions.js +16 -0
  54. package/src/utils/isRegExp.js +7 -0
  55. package/Detox-android/com/wix/detox/21.0.0-rc.3/detox-21.0.0-rc.3-javadoc.jar.md5 +0 -1
  56. package/Detox-android/com/wix/detox/21.0.0-rc.3/detox-21.0.0-rc.3-javadoc.jar.sha1 +0 -1
  57. package/Detox-android/com/wix/detox/21.0.0-rc.3/detox-21.0.0-rc.3-javadoc.jar.sha256 +0 -1
  58. package/Detox-android/com/wix/detox/21.0.0-rc.3/detox-21.0.0-rc.3-javadoc.jar.sha512 +0 -1
  59. package/Detox-android/com/wix/detox/21.0.0-rc.3/detox-21.0.0-rc.3-sources.jar.md5 +0 -1
  60. package/Detox-android/com/wix/detox/21.0.0-rc.3/detox-21.0.0-rc.3-sources.jar.sha1 +0 -1
  61. package/Detox-android/com/wix/detox/21.0.0-rc.3/detox-21.0.0-rc.3-sources.jar.sha256 +0 -1
  62. package/Detox-android/com/wix/detox/21.0.0-rc.3/detox-21.0.0-rc.3-sources.jar.sha512 +0 -1
  63. package/Detox-android/com/wix/detox/21.0.0-rc.3/detox-21.0.0-rc.3.aar +0 -0
  64. package/Detox-android/com/wix/detox/21.0.0-rc.3/detox-21.0.0-rc.3.aar.md5 +0 -1
  65. package/Detox-android/com/wix/detox/21.0.0-rc.3/detox-21.0.0-rc.3.aar.sha1 +0 -1
  66. package/Detox-android/com/wix/detox/21.0.0-rc.3/detox-21.0.0-rc.3.aar.sha256 +0 -1
  67. package/Detox-android/com/wix/detox/21.0.0-rc.3/detox-21.0.0-rc.3.aar.sha512 +0 -1
  68. package/Detox-android/com/wix/detox/21.0.0-rc.3/detox-21.0.0-rc.3.pom.md5 +0 -1
  69. package/Detox-android/com/wix/detox/21.0.0-rc.3/detox-21.0.0-rc.3.pom.sha1 +0 -1
  70. package/Detox-android/com/wix/detox/21.0.0-rc.3/detox-21.0.0-rc.3.pom.sha256 +0 -1
  71. package/Detox-android/com/wix/detox/21.0.0-rc.3/detox-21.0.0-rc.3.pom.sha512 +0 -1
  72. package/src/invoke/EarlGrey.js +0 -8
package/index.d.ts CHANGED
@@ -790,7 +790,7 @@ declare global {
790
790
  setLocation(lat: number, lon: number): Promise<void>;
791
791
 
792
792
  /**
793
- * Disable EarlGrey's network synchronization mechanism on preferred endpoints. Useful if you want to on skip over synchronizing on certain URLs.
793
+ * Disable network synchronization mechanism on preferred endpoints. Useful if you want to on skip over synchronizing on certain URLs.
794
794
  *
795
795
  * @example await device.setURLBlacklist(['.*127.0.0.1.*']);
796
796
  */
@@ -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.
@@ -1006,6 +1011,7 @@ declare global {
1006
1011
 
1007
1012
  interface ByWebFacade {
1008
1013
  /**
1014
+ * (Android Only)
1009
1015
  * Find an element on the DOM tree by its id
1010
1016
  * @param id
1011
1017
  * @example
@@ -1014,6 +1020,7 @@ declare global {
1014
1020
  id(id: string): WebMatcher;
1015
1021
 
1016
1022
  /**
1023
+ * (Android Only)
1017
1024
  * Find an element on the DOM tree by its CSS class
1018
1025
  * @param className
1019
1026
  * @example
@@ -1022,6 +1029,7 @@ declare global {
1022
1029
  className(className: string): WebMatcher;
1023
1030
 
1024
1031
  /**
1032
+ * (Android Only)
1025
1033
  * Find an element on the DOM tree matching the given CSS selector
1026
1034
  * @param cssSelector
1027
1035
  * @example
@@ -1030,6 +1038,7 @@ declare global {
1030
1038
  cssSelector(cssSelector: string): WebMatcher;
1031
1039
 
1032
1040
  /**
1041
+ * (Android Only)
1033
1042
  * Find an element on the DOM tree by its "name" attribute
1034
1043
  * @param name
1035
1044
  * @example
@@ -1038,6 +1047,7 @@ declare global {
1038
1047
  name(name: string): WebMatcher;
1039
1048
 
1040
1049
  /**
1050
+ * (Android Only)
1041
1051
  * Find an element on the DOM tree by its XPath
1042
1052
  * @param xpath
1043
1053
  * @example
@@ -1046,6 +1056,7 @@ declare global {
1046
1056
  xpath(xpath: string): WebMatcher;
1047
1057
 
1048
1058
  /**
1059
+ * (Android Only)
1049
1060
  * Find an <a> element on the DOM tree by its link text (href content)
1050
1061
  * @param linkText
1051
1062
  * @example
@@ -1054,6 +1065,7 @@ declare global {
1054
1065
  href(linkText: string): WebMatcher;
1055
1066
 
1056
1067
  /**
1068
+ * (Android Only)
1057
1069
  * Find an <a> element on the DOM tree by its partial link text (href content)
1058
1070
  * @param linkTextFragment
1059
1071
  * @example
@@ -1062,12 +1074,31 @@ declare global {
1062
1074
  hrefContains(linkTextFragment: string): WebMatcher;
1063
1075
 
1064
1076
  /**
1077
+ * (Android Only)
1065
1078
  * Find an element on the DOM tree by its tag name
1066
1079
  * @param tag
1067
1080
  * @example
1068
1081
  * web.element(by.web.tag('mark'))
1069
1082
  */
1070
1083
  tag(tagName: string): WebMatcher;
1084
+
1085
+ /**
1086
+ * (iOS Only)
1087
+ * Find an element on the web-view by its text content
1088
+ * @param label
1089
+ * @example
1090
+ * web.element(by.web.label('Welcome'))
1091
+ */
1092
+ label(label: string): WebMatcher;
1093
+
1094
+ /**
1095
+ * (iOS Only)
1096
+ * Find an element on the web-view by its content description
1097
+ * @param value
1098
+ * @example
1099
+ * web.element(by.web.value('Write a comment...'))
1100
+ */
1101
+ value(value: string): WebMatcher;
1071
1102
  }
1072
1103
 
1073
1104
  interface NativeMatcher {
@@ -1477,6 +1508,9 @@ declare global {
1477
1508
  }
1478
1509
 
1479
1510
  interface WebElementActions {
1511
+ /**
1512
+ * Taps the element
1513
+ */
1480
1514
  tap(): Promise<void>;
1481
1515
 
1482
1516
  /**
@@ -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": "21.0.0-rc.3",
4
+ "version": "21.0.0-rc.4",
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"
@@ -203,5 +204,8 @@
203
204
  }
204
205
  }
205
206
  },
206
- "gitHead": "85c7afa72ed35a3ed6d57eb0524a16b3935ef415"
207
+ "browserslist": [
208
+ "node 14"
209
+ ],
210
+ "gitHead": "72ae4a95d8115737031a400ba44243b3ae7a42e0"
207
211
  }
@@ -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
 
@@ -10,6 +10,7 @@ module.exports = {
10
10
  type: (value) => new native.TypeMatcher(value),
11
11
  value: (value) => new native.ValueMatcher(value),
12
12
 
13
+ // label and value not supported
13
14
  web: {
14
15
  id: (value) => new web.IdMatcher(value),
15
16
  className: (value) => new web.ClassNameMatcher(value),
@@ -19,5 +20,7 @@ module.exports = {
19
20
  xpath: (value) => new web.XPathMatcher(value),
20
21
  href: (value) => new web.LinkTextMatcher(value),
21
22
  hrefContains: (value) => new web.PartialLinkTextMatcher(value),
23
+ label: (value) => new web.LabelMatcher(value),
24
+ value: (value) => new web.ValueMatcher(value),
22
25
  },
23
26
  };
@@ -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
 
@@ -1,6 +1,7 @@
1
1
  const invoke = require('../../invoke');
2
2
  const { WebMatcher } = require('../core/WebMatcher');
3
3
  const DetoxWebMatcherApi = require('../espressoapi/web/DetoxWebAtomMatcher');
4
+ const DetoxRuntimeError = require('../../errors/DetoxRuntimeError');
4
5
 
5
6
  class IdMatcher extends WebMatcher {
6
7
  constructor(id) {
@@ -51,6 +52,20 @@ class PartialLinkTextMatcher extends WebMatcher {
51
52
  }
52
53
  }
53
54
 
55
+ class LabelMatcher extends WebMatcher {
56
+ constructor(_) {
57
+ super();
58
+ throw new DetoxRuntimeError('Label matching is not supported on Android');
59
+ }
60
+ }
61
+
62
+ class ValueMatcher extends WebMatcher {
63
+ constructor(_) {
64
+ super();
65
+ throw new DetoxRuntimeError('Label matching is not supported on Android');
66
+ }
67
+ }
68
+
54
69
  class TagNameMatcher extends WebMatcher {
55
70
  constructor(tag) {
56
71
  super();
@@ -66,5 +81,7 @@ module.exports = {
66
81
  XPathMatcher,
67
82
  LinkTextMatcher,
68
83
  PartialLinkTextMatcher,
69
- TagNameMatcher
84
+ TagNameMatcher,
85
+ LabelMatcher,
86
+ ValueMatcher
70
87
  };
@@ -101,9 +101,8 @@ function runXCUITest(
101
101
  };
102
102
 
103
103
  const options = {
104
- maxBuffer: 1024 * 1024 * 1024,
104
+ maxBuffer: 1024 * 1024 * 10, // 10MB
105
105
  retries: 1,
106
- verbosity: 'high',
107
106
  };
108
107
 
109
108
  const command = `${env.TEST_RUNNER_IS_DETOX_ACTIVE ? Object.keys(env).map(key => `${key}=${env[key]}`).join(' ') : ''} ${xcodebuildBinary} ${flags.map(flag => flag.includes(' ') ? `"${flag}"` : flag).join(' ')}`;
@@ -124,9 +123,9 @@ function runXCUITest(
124
123
  }
125
124
 
126
125
  function _runCommandInTerminal(command, options) {
127
- const escapedCommand = command.replace(/"/g, '\\"'); // escape double quotes
126
+ const escapedCommand = command.replace(/"/g, '\\"');
128
127
 
129
- // opens the Terminal app and runs the command in a new window, then closes the window when the command is done running.
128
+ // Opens the Terminal app and runs the command in a new window, then closes the window when the command is done running.
130
129
  const appleScript = `
131
130
  tell application "Terminal"
132
131
  if not running then
package/src/invoke.js CHANGED
@@ -1,4 +1,3 @@
1
- const EarlGrey = require('./invoke/EarlGrey');
2
1
  const Espresso = require('./invoke/Espresso');
3
2
  const EspressoWeb = require('./invoke/EspressoWeb');
4
3
  const Invoke = require('./invoke/Invoke');
@@ -15,7 +14,6 @@ class InvocationManager {
15
14
 
16
15
  module.exports = {
17
16
  InvocationManager,
18
- EarlGrey,
19
17
  Espresso: Espresso.target,
20
18
  EspressoWeb: EspressoWeb.target,
21
19
  IOS: Invoke.genericInvokeObject,
@@ -6,12 +6,16 @@ const fs = require('fs-extra');
6
6
  const _ = require('lodash');
7
7
  const tempfile = require('tempfile');
8
8
 
9
+
9
10
  const { assertEnum, assertNormalized } = require('../utils/assertArgument');
10
11
  const { removeMilliseconds } = require('../utils/dateUtils');
11
12
  const { actionDescription, expectDescription } = require('../utils/invocationTraceDescriptions');
13
+ const { isRegExp } = require('../utils/isRegExp');
12
14
  const log = require('../utils/logger').child({ cat: 'ws-client, ws' });
13
15
  const traceInvocationCall = require('../utils/traceInvocationCall').bind(null, log);
14
16
 
17
+ const { webElement, webMatcher, webExpect, isWebElement } = require('./web');
18
+
15
19
  const assertDirection = assertEnum(['left', 'right', 'up', 'down']);
16
20
  const assertSpeed = assertEnum(['fast', 'slow']);
17
21
 
@@ -231,7 +235,7 @@ class Element {
231
235
 
232
236
  performAccessibilityAction(actionName) {
233
237
  if (typeof actionName !== 'string') throw new Error('actionName should be a string, but got ' + (actionName + (' (' + (typeof actionName + ')'))));
234
-
238
+
235
239
  const traceDescription = actionDescription.performAccessibilityAction(actionName);
236
240
  return this.withAction('accessibilityAction', traceDescription, actionName);
237
241
  }
@@ -399,7 +403,7 @@ class By {
399
403
  }
400
404
 
401
405
  get web() {
402
- throw new Error('Detox does not support by.web matchers on iOS.');
406
+ return webMatcher();
403
407
  }
404
408
  }
405
409
 
@@ -409,14 +413,14 @@ class Matcher {
409
413
  }
410
414
 
411
415
  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 };
416
+ if (typeof label !== 'string' && !isRegExp(label)) throw new Error('label should be a string or regex, but got ' + (label + (' (' + (typeof label + ')'))));
417
+ this.predicate = { type: 'label', value: label.toString(), isRegex: isRegExp(label) };
414
418
  return this;
415
419
  }
416
420
 
417
421
  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 };
422
+ if (typeof id !== 'string' && !isRegExp(id)) throw new Error('id should be a string or regex, but got ' + (id + (' (' + (typeof id + ')'))));
423
+ this.predicate = { type: 'id', value: id.toString(), isRegex: isRegExp(id) };
420
424
  return this;
421
425
  }
422
426
 
@@ -439,8 +443,8 @@ class Matcher {
439
443
  }
440
444
 
441
445
  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 };
446
+ if (typeof text !== 'string' && !isRegExp(text)) throw new Error(`text should be a string or regex, but got ` + (text + (' (' + (typeof text + ')'))));
447
+ this.predicate = { type: 'text', value: text.toString(), isRegex: isRegExp(text) };
444
448
  return this;
445
449
  }
446
450
 
@@ -754,7 +758,7 @@ class IosExpect {
754
758
  this.waitFor = this.waitFor.bind(this);
755
759
  this.by = new By();
756
760
  this.web = this.web.bind(this);
757
- this.web.element = this.web;
761
+ this.web.element = this.web().element;
758
762
  }
759
763
 
760
764
  element(matcher) {
@@ -762,6 +766,10 @@ class IosExpect {
762
766
  }
763
767
 
764
768
  expect(element) {
769
+ if (isWebElement(element)) {
770
+ return webExpect(this._invocationManager, element);
771
+ }
772
+
765
773
  return expect(this._invocationManager, element);
766
774
  }
767
775
 
@@ -769,8 +777,17 @@ class IosExpect {
769
777
  return waitFor(this._invocationManager, this._emitter, element);
770
778
  }
771
779
 
772
- web(_matcher) {
773
- throw new Error('Detox does not support web(), web.element() API on iOS.');
780
+ web(matcher) {
781
+ return {
782
+ element: webMatcher => {
783
+ if (!(matcher instanceof Matcher) && matcher !== undefined) {
784
+ throwMatcherError(matcher);
785
+ }
786
+
787
+ const webViewElement = matcher ? element(this._invocationManager, this._emitter, matcher) : undefined;
788
+ return webElement(this._invocationManager, this._emitter, webViewElement, webMatcher);
789
+ }
790
+ };
774
791
  }
775
792
  }
776
793