appium-xcuitest-driver 10.2.2 → 10.4.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 (262) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/build/lib/commands/active-app-info.d.ts +9 -0
  3. package/build/lib/commands/active-app-info.d.ts.map +1 -0
  4. package/build/lib/commands/active-app-info.js +14 -0
  5. package/build/lib/commands/active-app-info.js.map +1 -0
  6. package/build/lib/commands/alert.d.ts +42 -45
  7. package/build/lib/commands/alert.d.ts.map +1 -1
  8. package/build/lib/commands/alert.js +66 -62
  9. package/build/lib/commands/alert.js.map +1 -1
  10. package/build/lib/commands/app-management.d.ts +150 -153
  11. package/build/lib/commands/app-management.d.ts.map +1 -1
  12. package/build/lib/commands/app-management.js +300 -286
  13. package/build/lib/commands/app-management.js.map +1 -1
  14. package/build/lib/commands/app-strings.d.ts +14 -17
  15. package/build/lib/commands/app-strings.d.ts.map +1 -1
  16. package/build/lib/commands/app-strings.js +23 -24
  17. package/build/lib/commands/app-strings.js.map +1 -1
  18. package/build/lib/commands/appearance.d.ts +19 -22
  19. package/build/lib/commands/appearance.d.ts.map +1 -1
  20. package/build/lib/commands/appearance.js +56 -56
  21. package/build/lib/commands/appearance.js.map +1 -1
  22. package/build/lib/commands/audit.d.ts +22 -17
  23. package/build/lib/commands/audit.d.ts.map +1 -1
  24. package/build/lib/commands/audit.js +17 -18
  25. package/build/lib/commands/audit.js.map +1 -1
  26. package/build/lib/commands/battery.d.ts +11 -14
  27. package/build/lib/commands/battery.d.ts.map +1 -1
  28. package/build/lib/commands/battery.js +36 -37
  29. package/build/lib/commands/battery.js.map +1 -1
  30. package/build/lib/commands/biometric.d.ts +30 -33
  31. package/build/lib/commands/biometric.d.ts.map +1 -1
  32. package/build/lib/commands/biometric.js +42 -41
  33. package/build/lib/commands/biometric.js.map +1 -1
  34. package/build/lib/commands/certificate.d.ts +48 -45
  35. package/build/lib/commands/certificate.d.ts.map +1 -1
  36. package/build/lib/commands/certificate.js +218 -205
  37. package/build/lib/commands/certificate.js.map +1 -1
  38. package/build/lib/commands/clipboard.d.ts +19 -22
  39. package/build/lib/commands/clipboard.d.ts.map +1 -1
  40. package/build/lib/commands/clipboard.js +30 -30
  41. package/build/lib/commands/clipboard.js.map +1 -1
  42. package/build/lib/commands/condition.d.ts +49 -26
  43. package/build/lib/commands/condition.d.ts.map +1 -1
  44. package/build/lib/commands/condition.js +87 -86
  45. package/build/lib/commands/condition.js.map +1 -1
  46. package/build/lib/commands/content-size.d.ts +26 -29
  47. package/build/lib/commands/content-size.d.ts.map +1 -1
  48. package/build/lib/commands/content-size.js +36 -36
  49. package/build/lib/commands/content-size.js.map +1 -1
  50. package/build/lib/commands/context.d.ts +161 -108
  51. package/build/lib/commands/context.d.ts.map +1 -1
  52. package/build/lib/commands/context.js +530 -517
  53. package/build/lib/commands/context.js.map +1 -1
  54. package/build/lib/commands/deviceInfo.d.ts +9 -12
  55. package/build/lib/commands/deviceInfo.d.ts.map +1 -1
  56. package/build/lib/commands/deviceInfo.js +17 -18
  57. package/build/lib/commands/deviceInfo.js.map +1 -1
  58. package/build/lib/commands/element.d.ts +102 -105
  59. package/build/lib/commands/element.d.ts.map +1 -1
  60. package/build/lib/commands/element.js +337 -323
  61. package/build/lib/commands/element.js.map +1 -1
  62. package/build/lib/commands/execute.d.ts +24 -19
  63. package/build/lib/commands/execute.d.ts.map +1 -1
  64. package/build/lib/commands/execute.js +63 -62
  65. package/build/lib/commands/execute.js.map +1 -1
  66. package/build/lib/commands/file-movement.d.ts +77 -80
  67. package/build/lib/commands/file-movement.d.ts.map +1 -1
  68. package/build/lib/commands/file-movement.js +130 -124
  69. package/build/lib/commands/file-movement.js.map +1 -1
  70. package/build/lib/commands/find.d.ts +18 -21
  71. package/build/lib/commands/find.d.ts.map +1 -1
  72. package/build/lib/commands/find.js +158 -156
  73. package/build/lib/commands/find.js.map +1 -1
  74. package/build/lib/commands/general.d.ts +124 -116
  75. package/build/lib/commands/general.d.ts.map +1 -1
  76. package/build/lib/commands/general.js +248 -232
  77. package/build/lib/commands/general.js.map +1 -1
  78. package/build/lib/commands/geolocation.d.ts +43 -46
  79. package/build/lib/commands/geolocation.d.ts.map +1 -1
  80. package/build/lib/commands/geolocation.js +10 -11
  81. package/build/lib/commands/geolocation.js.map +1 -1
  82. package/build/lib/commands/gesture.d.ts +273 -276
  83. package/build/lib/commands/gesture.d.ts.map +1 -1
  84. package/build/lib/commands/gesture.js +506 -492
  85. package/build/lib/commands/gesture.js.map +1 -1
  86. package/build/lib/commands/increase-contrast.d.ts +20 -23
  87. package/build/lib/commands/increase-contrast.d.ts.map +1 -1
  88. package/build/lib/commands/increase-contrast.js +30 -30
  89. package/build/lib/commands/increase-contrast.js.map +1 -1
  90. package/build/lib/commands/iohid.d.ts +1370 -1373
  91. package/build/lib/commands/iohid.d.ts.map +1 -1
  92. package/build/lib/commands/iohid.js +30 -31
  93. package/build/lib/commands/iohid.js.map +1 -1
  94. package/build/lib/commands/keyboard.d.ts +29 -32
  95. package/build/lib/commands/keyboard.d.ts.map +1 -1
  96. package/build/lib/commands/keyboard.js +53 -51
  97. package/build/lib/commands/keyboard.js.map +1 -1
  98. package/build/lib/commands/keychains.d.ts +9 -12
  99. package/build/lib/commands/keychains.d.ts.map +1 -1
  100. package/build/lib/commands/keychains.js +13 -14
  101. package/build/lib/commands/keychains.js.map +1 -1
  102. package/build/lib/commands/localization.d.ts +16 -19
  103. package/build/lib/commands/localization.d.ts.map +1 -1
  104. package/build/lib/commands/localization.js +25 -26
  105. package/build/lib/commands/localization.js.map +1 -1
  106. package/build/lib/commands/location.d.ts +36 -39
  107. package/build/lib/commands/location.d.ts.map +1 -1
  108. package/build/lib/commands/location.js +99 -98
  109. package/build/lib/commands/location.js.map +1 -1
  110. package/build/lib/commands/lock.d.ts +21 -24
  111. package/build/lib/commands/lock.d.ts.map +1 -1
  112. package/build/lib/commands/lock.js +39 -38
  113. package/build/lib/commands/lock.js.map +1 -1
  114. package/build/lib/commands/log.d.ts +43 -37
  115. package/build/lib/commands/log.d.ts.map +1 -1
  116. package/build/lib/commands/log.js +174 -171
  117. package/build/lib/commands/log.js.map +1 -1
  118. package/build/lib/commands/memory.d.ts +9 -12
  119. package/build/lib/commands/memory.d.ts.map +1 -1
  120. package/build/lib/commands/memory.js +37 -39
  121. package/build/lib/commands/memory.js.map +1 -1
  122. package/build/lib/commands/navigation.d.ts +30 -33
  123. package/build/lib/commands/navigation.d.ts.map +1 -1
  124. package/build/lib/commands/navigation.js +92 -92
  125. package/build/lib/commands/navigation.js.map +1 -1
  126. package/build/lib/commands/notifications.d.ts +26 -29
  127. package/build/lib/commands/notifications.d.ts.map +1 -1
  128. package/build/lib/commands/notifications.js +53 -53
  129. package/build/lib/commands/notifications.js.map +1 -1
  130. package/build/lib/commands/pasteboard.d.ts +21 -24
  131. package/build/lib/commands/pasteboard.d.ts.map +1 -1
  132. package/build/lib/commands/pasteboard.js +37 -37
  133. package/build/lib/commands/pasteboard.js.map +1 -1
  134. package/build/lib/commands/pcap.d.ts +39 -26
  135. package/build/lib/commands/pcap.d.ts.map +1 -1
  136. package/build/lib/commands/pcap.js +81 -81
  137. package/build/lib/commands/pcap.js.map +1 -1
  138. package/build/lib/commands/performance.d.ts +63 -44
  139. package/build/lib/commands/performance.d.ts.map +1 -1
  140. package/build/lib/commands/performance.js +105 -105
  141. package/build/lib/commands/performance.js.map +1 -1
  142. package/build/lib/commands/permissions.d.ts +33 -36
  143. package/build/lib/commands/permissions.d.ts.map +1 -1
  144. package/build/lib/commands/permissions.js +66 -65
  145. package/build/lib/commands/permissions.js.map +1 -1
  146. package/build/lib/commands/proxy-helper.d.ts +12 -15
  147. package/build/lib/commands/proxy-helper.d.ts.map +1 -1
  148. package/build/lib/commands/proxy-helper.js +53 -54
  149. package/build/lib/commands/proxy-helper.js.map +1 -1
  150. package/build/lib/commands/record-audio.d.ts +49 -29
  151. package/build/lib/commands/record-audio.d.ts.map +1 -1
  152. package/build/lib/commands/record-audio.js +100 -104
  153. package/build/lib/commands/record-audio.js.map +1 -1
  154. package/build/lib/commands/recordscreen.d.ts +54 -18
  155. package/build/lib/commands/recordscreen.d.ts.map +1 -1
  156. package/build/lib/commands/recordscreen.js +127 -129
  157. package/build/lib/commands/recordscreen.js.map +1 -1
  158. package/build/lib/commands/screenshots.d.ts +14 -17
  159. package/build/lib/commands/screenshots.d.ts.map +1 -1
  160. package/build/lib/commands/screenshots.js +108 -107
  161. package/build/lib/commands/screenshots.js.map +1 -1
  162. package/build/lib/commands/simctl.d.ts +11 -14
  163. package/build/lib/commands/simctl.d.ts.map +1 -1
  164. package/build/lib/commands/simctl.js +23 -26
  165. package/build/lib/commands/simctl.js.map +1 -1
  166. package/build/lib/commands/source.d.ts +14 -17
  167. package/build/lib/commands/source.d.ts.map +1 -1
  168. package/build/lib/commands/source.js +40 -43
  169. package/build/lib/commands/source.js.map +1 -1
  170. package/build/lib/commands/timeouts.d.ts +44 -33
  171. package/build/lib/commands/timeouts.d.ts.map +1 -1
  172. package/build/lib/commands/timeouts.js +65 -63
  173. package/build/lib/commands/timeouts.js.map +1 -1
  174. package/build/lib/commands/web.d.ts +247 -197
  175. package/build/lib/commands/web.d.ts.map +1 -1
  176. package/build/lib/commands/web.js +815 -786
  177. package/build/lib/commands/web.js.map +1 -1
  178. package/build/lib/commands/xctest-record-screen.d.ts +63 -66
  179. package/build/lib/commands/xctest-record-screen.d.ts.map +1 -1
  180. package/build/lib/commands/xctest-record-screen.js +103 -102
  181. package/build/lib/commands/xctest-record-screen.js.map +1 -1
  182. package/build/lib/commands/xctest.d.ts +55 -51
  183. package/build/lib/commands/xctest.d.ts.map +1 -1
  184. package/build/lib/commands/xctest.js +116 -117
  185. package/build/lib/commands/xctest.js.map +1 -1
  186. package/build/lib/driver.d.ts +278 -1597
  187. package/build/lib/driver.d.ts.map +1 -1
  188. package/build/lib/driver.js +320 -236
  189. package/build/lib/driver.js.map +1 -1
  190. package/build/lib/execute-method-map.d.ts.map +1 -1
  191. package/build/lib/execute-method-map.js +9 -0
  192. package/build/lib/execute-method-map.js.map +1 -1
  193. package/build/lib/real-device.d.ts +1 -1
  194. package/build/lib/real-device.d.ts.map +1 -1
  195. package/build/lib/real-device.js +2 -2
  196. package/build/lib/real-device.js.map +1 -1
  197. package/lib/commands/active-app-info.js +12 -0
  198. package/lib/commands/alert.js +68 -65
  199. package/lib/commands/app-management.js +308 -301
  200. package/lib/commands/app-strings.js +24 -26
  201. package/lib/commands/appearance.js +54 -56
  202. package/lib/commands/audit.js +18 -20
  203. package/lib/commands/battery.js +35 -37
  204. package/lib/commands/biometric.js +44 -46
  205. package/lib/commands/certificate.js +226 -215
  206. package/lib/commands/clipboard.js +30 -32
  207. package/lib/commands/condition.js +98 -100
  208. package/lib/commands/content-size.js +36 -38
  209. package/lib/commands/context.js +495 -490
  210. package/lib/commands/deviceInfo.js +19 -20
  211. package/lib/commands/element.js +367 -357
  212. package/lib/commands/execute.js +72 -72
  213. package/lib/commands/file-movement.js +132 -134
  214. package/lib/commands/find.js +160 -159
  215. package/lib/commands/general.js +238 -231
  216. package/lib/commands/geolocation.js +6 -14
  217. package/lib/commands/gesture.js +525 -515
  218. package/lib/commands/increase-contrast.js +30 -32
  219. package/lib/commands/iohid.js +32 -34
  220. package/lib/commands/keyboard.js +49 -51
  221. package/lib/commands/keychains.js +12 -14
  222. package/lib/commands/localization.js +24 -26
  223. package/lib/commands/location.js +102 -104
  224. package/lib/commands/lock.js +38 -38
  225. package/lib/commands/log.js +197 -198
  226. package/lib/commands/memory.js +40 -43
  227. package/lib/commands/navigation.js +96 -100
  228. package/lib/commands/notifications.js +57 -59
  229. package/lib/commands/pasteboard.js +37 -39
  230. package/lib/commands/pcap.js +84 -86
  231. package/lib/commands/performance.js +132 -133
  232. package/lib/commands/permissions.js +67 -69
  233. package/lib/commands/proxy-helper.js +60 -61
  234. package/lib/commands/record-audio.js +115 -120
  235. package/lib/commands/recordscreen.js +145 -149
  236. package/lib/commands/screenshots.js +116 -116
  237. package/lib/commands/simctl.js +25 -29
  238. package/lib/commands/source.js +42 -46
  239. package/lib/commands/timeouts.js +59 -63
  240. package/lib/commands/web.js +878 -858
  241. package/lib/commands/xctest-record-screen.js +103 -105
  242. package/lib/commands/xctest.js +134 -139
  243. package/lib/driver.js +288 -236
  244. package/lib/execute-method-map.ts +9 -0
  245. package/lib/real-device.js +2 -2
  246. package/npm-shrinkwrap.json +440 -76
  247. package/package.json +2 -1
  248. package/build/lib/commands/activeAppInfo.d.ts +0 -12
  249. package/build/lib/commands/activeAppInfo.d.ts.map +0 -1
  250. package/build/lib/commands/activeAppInfo.js +0 -15
  251. package/build/lib/commands/activeAppInfo.js.map +0 -1
  252. package/build/lib/commands/index.d.ts +0 -96
  253. package/build/lib/commands/index.d.ts.map +0 -1
  254. package/build/lib/commands/index.js +0 -100
  255. package/build/lib/commands/index.js.map +0 -1
  256. package/build/lib/real-device-clients/devicectl.d.ts +0 -204
  257. package/build/lib/real-device-clients/devicectl.d.ts.map +0 -1
  258. package/build/lib/real-device-clients/devicectl.js +0 -264
  259. package/build/lib/real-device-clients/devicectl.js.map +0 -1
  260. package/lib/commands/activeAppInfo.js +0 -14
  261. package/lib/commands/index.js +0 -95
  262. package/lib/real-device-clients/devicectl.js +0 -291
@@ -36,6 +36,40 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
36
36
  return (mod && mod.__esModule) ? mod : { "default": mod };
37
37
  };
38
38
  Object.defineProperty(exports, "__esModule", { value: true });
39
+ exports.setFrame = setFrame;
40
+ exports.getCssProperty = getCssProperty;
41
+ exports.submit = submit;
42
+ exports.refresh = refresh;
43
+ exports.getUrl = getUrl;
44
+ exports.title = title;
45
+ exports.getCookies = getCookies;
46
+ exports.setCookie = setCookie;
47
+ exports.deleteCookie = deleteCookie;
48
+ exports.deleteCookies = deleteCookies;
49
+ exports._deleteCookie = _deleteCookie;
50
+ exports.cacheWebElement = cacheWebElement;
51
+ exports.cacheWebElements = cacheWebElements;
52
+ exports.executeAtom = executeAtom;
53
+ exports.executeAtomAsync = executeAtomAsync;
54
+ exports.getAtomsElement = getAtomsElement;
55
+ exports.convertElementsForAtoms = convertElementsForAtoms;
56
+ exports.getElementId = getElementId;
57
+ exports.hasElementId = hasElementId;
58
+ exports.findWebElementOrElements = findWebElementOrElements;
59
+ exports.clickWebCoords = clickWebCoords;
60
+ exports.getSafariIsIphone = getSafariIsIphone;
61
+ exports.getSafariDeviceSize = getSafariDeviceSize;
62
+ exports.getSafariIsNotched = getSafariIsNotched;
63
+ exports.getExtraTranslateWebCoordsOffset = getExtraTranslateWebCoordsOffset;
64
+ exports.getExtraNativeWebTapOffset = getExtraNativeWebTapOffset;
65
+ exports.nativeWebTap = nativeWebTap;
66
+ exports.translateWebCoords = translateWebCoords;
67
+ exports.checkForAlert = checkForAlert;
68
+ exports.waitForAtom = waitForAtom;
69
+ exports.mobileWebNav = mobileWebNav;
70
+ exports.getWdaLocalhostRoot = getWdaLocalhostRoot;
71
+ exports.mobileCalibrateWebToRealCoordinatesTranslation = mobileCalibrateWebToRealCoordinatesTranslation;
72
+ exports.mobileUpdateSafariPreferences = mobileUpdateSafariPreferences;
39
73
  const driver_1 = require("appium/driver");
40
74
  const support_1 = require("appium/support");
41
75
  const asyncbox_1 = require("asyncbox");
@@ -82,840 +116,776 @@ const TAB_BAR_POSITION_BOTTOM = 'bottom';
82
116
  const TAB_BAR_POSSITIONS = [TAB_BAR_POSITION_TOP, TAB_BAR_POSITION_BOTTOM];
83
117
  /**
84
118
  * @this {XCUITestDriver}
85
- * @param {any} atomsElement
86
- * @returns {Promise<boolean>}
119
+ * @group Mobile Web Only
120
+ * @param {number|string|null} frame
121
+ * @returns {Promise<void>}
87
122
  */
88
- async function tapWebElementNatively(atomsElement) {
89
- // try to get the text of the element, which will be accessible in the
90
- // native context
91
- try {
92
- const [text1, text2] = await bluebird_1.default.all([
93
- this.executeAtom('get_text', [atomsElement]),
94
- this.executeAtom('get_attribute_value', [atomsElement, 'value'])
95
- ]);
96
- const text = text1 || text2;
97
- if (!text) {
98
- return false;
99
- }
100
- const els = await this.findNativeElementOrElements('accessibility id', text, true);
101
- if (![1, 2].includes(els.length)) {
102
- return false;
103
- }
104
- const el = els[0];
105
- // use tap because on iOS 11.2 and below `nativeClick` crashes WDA
106
- const rect = /** @type {import('@appium/types').Rect} */ (await this.proxyCommand(`/element/${support_1.util.unwrapElement(el)}/rect`, 'GET'));
107
- if (els.length > 1) {
108
- const el2 = els[1];
109
- const rect2 = /** @type {import('@appium/types').Rect} */ (await this.proxyCommand(`/element/${support_1.util.unwrapElement(el2)}/rect`, 'GET'));
110
- if (rect.x !== rect2.x || rect.y !== rect2.y
111
- || rect.width !== rect2.width || rect.height !== rect2.height) {
112
- // These 2 native elements are not referring to the same web element
113
- return false;
114
- }
115
- }
116
- await this.mobileTap(rect.x + rect.width / 2, rect.y + rect.height / 2);
117
- return true;
123
+ async function setFrame(frame) {
124
+ if (!this.isWebContext()) {
125
+ throw new driver_1.errors.NotImplementedError();
118
126
  }
119
- catch (err) {
120
- // any failure should fall through and trigger the more elaborate
121
- // method of clicking
122
- this.log.warn(`Error attempting to click: ${err.message}`);
127
+ if (lodash_1.default.isNull(frame)) {
128
+ this.curWebFrames = [];
129
+ this.log.debug('Leaving web frame and going back to default content');
130
+ return;
131
+ }
132
+ if (hasElementId(frame)) {
133
+ const atomsElement = this.getAtomsElement(frame);
134
+ const value = await this.executeAtom('get_frame_window', [atomsElement]);
135
+ this.log.debug(`Entering new web frame: '${value.WINDOW}'`);
136
+ this.curWebFrames.unshift(value.WINDOW);
137
+ }
138
+ else {
139
+ const atom = lodash_1.default.isNumber(frame) ? 'frame_by_index' : 'frame_by_id_or_name';
140
+ const value = await this.executeAtom(atom, [frame]);
141
+ if (lodash_1.default.isNull(value) || lodash_1.default.isUndefined(value.WINDOW)) {
142
+ throw new driver_1.errors.NoSuchFrameError();
143
+ }
144
+ this.log.debug(`Entering new web frame: '${value.WINDOW}'`);
145
+ this.curWebFrames.unshift(value.WINDOW);
123
146
  }
124
- return false;
125
147
  }
126
148
  /**
127
- * @param {any} id
128
- * @returns {boolean}
149
+ * @this {XCUITestDriver}
150
+ * @group Mobile Web Only
129
151
  */
130
- function isValidElementIdentifier(id) {
131
- if (!lodash_1.default.isString(id) && !lodash_1.default.isNumber(id)) {
132
- return false;
152
+ async function getCssProperty(propertyName, el) {
153
+ if (!this.isWebContext()) {
154
+ throw new driver_1.errors.NotImplementedError();
133
155
  }
134
- if (lodash_1.default.isString(id) && lodash_1.default.isEmpty(id)) {
135
- return false;
156
+ const atomsElement = this.getAtomsElement(el);
157
+ return await this.executeAtom('get_value_of_css_property', [atomsElement, propertyName]);
158
+ }
159
+ /**
160
+ * Submit the form an element is in
161
+ *
162
+ * @param {string|Element} el - the element ID
163
+ * @group Mobile Web Only
164
+ * @this {XCUITestDriver}
165
+ */
166
+ async function submit(el) {
167
+ if (!this.isWebContext()) {
168
+ throw new driver_1.errors.NotImplementedError();
136
169
  }
137
- if (lodash_1.default.isNumber(id) && isNaN(id)) {
138
- return false;
170
+ const atomsElement = this.getAtomsElement(el);
171
+ await this.executeAtom('submit', [atomsElement]);
172
+ }
173
+ /**
174
+ * @this {XCUITestDriver}
175
+ * @group Mobile Web Only
176
+ */
177
+ async function refresh() {
178
+ if (!this.isWebContext()) {
179
+ throw new driver_1.errors.NotImplementedError();
139
180
  }
140
- return true;
181
+ await ( /** @type {RemoteDebugger} */(this.remote)).execute('window.location.reload()');
141
182
  }
142
- const commands = {
143
- /**
144
- * @this {XCUITestDriver}
145
- * @group Mobile Web Only
146
- */
147
- async setFrame(frame) {
148
- if (!this.isWebContext()) {
149
- throw new driver_1.errors.NotImplementedError();
150
- }
151
- if (lodash_1.default.isNull(frame)) {
152
- this.curWebFrames = [];
153
- this.log.debug('Leaving web frame and going back to default content');
154
- return;
155
- }
156
- if (helpers.hasElementId(frame)) {
157
- const atomsElement = this.getAtomsElement(frame);
158
- const value = await this.executeAtom('get_frame_window', [atomsElement]);
159
- this.log.debug(`Entering new web frame: '${value.WINDOW}'`);
160
- this.curWebFrames.unshift(value.WINDOW);
161
- }
162
- else {
163
- const atom = lodash_1.default.isNumber(frame) ? 'frame_by_index' : 'frame_by_id_or_name';
164
- const value = await this.executeAtom(atom, [frame]);
165
- if (lodash_1.default.isNull(value) || lodash_1.default.isUndefined(value.WINDOW)) {
166
- throw new driver_1.errors.NoSuchFrameError();
183
+ /**
184
+ * @this {XCUITestDriver}
185
+ * @group Mobile Web Only
186
+ */
187
+ async function getUrl() {
188
+ if (!this.isWebContext()) {
189
+ throw new driver_1.errors.NotImplementedError();
190
+ }
191
+ return await ( /** @type {RemoteDebugger} */(this.remote)).execute('window.location.href');
192
+ }
193
+ /**
194
+ * @this {XCUITestDriver}
195
+ * @group Mobile Web Only
196
+ */
197
+ async function title() {
198
+ if (!this.isWebContext()) {
199
+ throw new driver_1.errors.NotImplementedError();
200
+ }
201
+ return await ( /** @type {RemoteDebugger} */(this.remote)).execute('window.document.title');
202
+ }
203
+ /**
204
+ * @this {XCUITestDriver}
205
+ * @group Mobile Web Only
206
+ */
207
+ async function getCookies() {
208
+ if (!this.isWebContext()) {
209
+ throw new driver_1.errors.NotImplementedError();
210
+ }
211
+ // get the cookies from the remote debugger, or an empty object
212
+ const { cookies } = await ( /** @type {RemoteDebugger} */(this.remote)).getCookies();
213
+ // the value is URI encoded, so decode it safely
214
+ return cookies.map((cookie) => {
215
+ if (!lodash_1.default.isEmpty(cookie.value)) {
216
+ try {
217
+ cookie.value = decodeURI(cookie.value);
167
218
  }
168
- this.log.debug(`Entering new web frame: '${value.WINDOW}'`);
169
- this.curWebFrames.unshift(value.WINDOW);
170
- }
171
- },
172
- /**
173
- * @this {XCUITestDriver}
174
- * @group Mobile Web Only
175
- */
176
- async getCssProperty(propertyName, el) {
177
- if (!this.isWebContext()) {
178
- throw new driver_1.errors.NotImplementedError();
179
- }
180
- const atomsElement = this.getAtomsElement(el);
181
- return await this.executeAtom('get_value_of_css_property', [atomsElement, propertyName]);
182
- },
183
- /**
184
- * Submit the form an element is in
185
- *
186
- * @param {string|Element} el - the element ID
187
- * @group Mobile Web Only
188
- * @this {XCUITestDriver}
189
- */
190
- async submit(el) {
191
- if (!this.isWebContext()) {
192
- throw new driver_1.errors.NotImplementedError();
193
- }
194
- const atomsElement = this.getAtomsElement(el);
195
- await this.executeAtom('submit', [atomsElement]);
196
- },
197
- /**
198
- * @this {XCUITestDriver}
199
- * @group Mobile Web Only
200
- */
201
- async refresh() {
202
- if (!this.isWebContext()) {
203
- throw new driver_1.errors.NotImplementedError();
204
- }
205
- await ( /** @type {RemoteDebugger} */(this.remote)).execute('window.location.reload()');
206
- },
207
- /**
208
- * @this {XCUITestDriver}
209
- * @group Mobile Web Only
210
- */
211
- async getUrl() {
212
- if (!this.isWebContext()) {
213
- throw new driver_1.errors.NotImplementedError();
214
- }
215
- return await ( /** @type {RemoteDebugger} */(this.remote)).execute('window.location.href');
216
- },
217
- /**
218
- * @this {XCUITestDriver}
219
- * @group Mobile Web Only
220
- */
221
- async title() {
222
- if (!this.isWebContext()) {
223
- throw new driver_1.errors.NotImplementedError();
224
- }
225
- return await ( /** @type {RemoteDebugger} */(this.remote)).execute('window.document.title');
226
- },
227
- /**
228
- * @this {XCUITestDriver}
229
- * @group Mobile Web Only
230
- */
231
- async getCookies() {
232
- if (!this.isWebContext()) {
233
- throw new driver_1.errors.NotImplementedError();
234
- }
235
- // get the cookies from the remote debugger, or an empty object
236
- const { cookies } = await ( /** @type {RemoteDebugger} */(this.remote)).getCookies();
237
- // the value is URI encoded, so decode it safely
238
- return cookies.map((cookie) => {
239
- if (!lodash_1.default.isEmpty(cookie.value)) {
240
- try {
241
- cookie.value = decodeURI(cookie.value);
242
- }
243
- catch (error) {
244
- this.log.debug(`Cookie ${cookie.name} was not decoded successfully. Cookie value: ${cookie.value}`);
245
- this.log.warn(error);
246
- // Keep the original value
247
- }
219
+ catch (error) {
220
+ this.log.debug(`Cookie ${cookie.name} was not decoded successfully. Cookie value: ${cookie.value}`);
221
+ this.log.warn(error);
222
+ // Keep the original value
248
223
  }
249
- return cookie;
250
- });
251
- },
252
- /**
253
- * @this {XCUITestDriver}
254
- * @group Mobile Web Only
255
- */
256
- async setCookie(cookie) {
257
- if (!this.isWebContext()) {
258
- throw new driver_1.errors.NotImplementedError();
259
- }
260
- cookie = lodash_1.default.clone(cookie);
261
- // if `path` field is not specified, Safari will not update cookies as expected; eg issue #1708
262
- if (!cookie.path) {
263
- cookie.path = '/';
264
224
  }
265
- const jsCookie = cookieUtils.createJSCookie(cookie.name, cookie.value, {
266
- expires: lodash_1.default.isNumber(cookie.expiry)
267
- ? new Date(cookie.expiry * 1000).toUTCString()
268
- : cookie.expiry,
269
- path: cookie.path,
270
- domain: cookie.domain,
271
- httpOnly: cookie.httpOnly,
272
- secure: cookie.secure,
273
- });
274
- let script = `document.cookie = ${JSON.stringify(jsCookie)}`;
275
- await this.executeAtom('execute_script', [script, []]);
276
- },
277
- /**
278
- * @this {XCUITestDriver}
279
- * @group Mobile Web Only
280
- */
281
- async deleteCookie(cookieName) {
282
- if (!this.isWebContext()) {
283
- throw new driver_1.errors.NotImplementedError();
284
- }
285
- const cookies = await this.getCookies();
286
- const cookie = cookies.find(({ name }) => name === cookieName);
287
- if (!cookie) {
288
- this.log.debug(`Cookie '${cookieName}' not found. Ignoring.`);
289
- return;
290
- }
291
- await this._deleteCookie(cookie);
292
- },
293
- /**
294
- * @this {XCUITestDriver}
295
- * @group Mobile Web Only
296
- */
297
- async deleteCookies() {
298
- if (!this.isWebContext()) {
299
- throw new driver_1.errors.NotImplementedError();
300
- }
301
- const cookies = await this.getCookies();
302
- await bluebird_1.default.all(cookies.map((cookie) => this._deleteCookie(cookie)));
303
- },
304
- };
305
- const helpers = {
306
- /**
307
- * @this {XCUITestDriver}
308
- */
309
- async _deleteCookie(cookie) {
310
- const url = `http${cookie.secure ? 's' : ''}://${cookie.domain}${cookie.path}`;
311
- return await ( /** @type {RemoteDebugger} */(this.remote)).deleteCookie(cookie.name, url);
312
- },
313
- /**
314
- * @this {XCUITestDriver}
315
- */
316
- cacheWebElement(el) {
317
- if (!lodash_1.default.isPlainObject(el)) {
318
- return el;
319
- }
320
- const elId = support_1.util.unwrapElement(el);
321
- if (!isValidElementIdentifier(elId)) {
322
- return el;
323
- }
324
- // In newer debugger releases element identifiers look like `:wdc:1628151649325`
325
- // We assume it is safe to use these to identify cached elements
326
- const cacheId = lodash_1.default.includes(elId, ':') ? elId : support_1.util.uuidV4();
327
- this.webElementsCache.set(cacheId, elId);
328
- return support_1.util.wrapElement(cacheId);
329
- },
330
- /**
331
- * @this {XCUITestDriver}
332
- */
333
- cacheWebElements(response) {
334
- const toCached = (v) => (lodash_1.default.isArray(v) || lodash_1.default.isPlainObject(v) ? this.cacheWebElements(v) : v);
335
- if (lodash_1.default.isArray(response)) {
336
- return response.map(toCached);
337
- }
338
- else if (lodash_1.default.isPlainObject(response)) {
339
- const result = { ...response, ...this.cacheWebElement(response) };
340
- return lodash_1.default.toPairs(result).reduce((acc, [key, value]) => {
341
- acc[key] = toCached(value);
342
- return acc;
343
- }, {});
344
- }
345
- return response;
346
- },
347
- /**
348
- * @param {string} atom
349
- * @param {unknown[]} args
350
- * @returns {Promise<any>}
351
- * @privateRemarks This should return `Promise<T>` where `T` extends `unknown`, but that's going to cause a lot of things to break.
352
- * @this {XCUITestDriver}
353
- */
354
- async executeAtom(atom, args, alwaysDefaultFrame = false) {
355
- let frames = alwaysDefaultFrame === true ? [] : this.curWebFrames;
356
- let promise = ( /** @type {RemoteDebugger} */(this.remote)).executeAtom(atom, args, frames);
357
- return await this.waitForAtom(promise);
358
- },
359
- /**
360
- * @this {XCUITestDriver}
361
- * @param {string} atom
362
- * @param {any[]} args
363
- */
364
- async executeAtomAsync(atom, args) {
365
- // save the resolve and reject methods of the promise to be waited for
366
- let promise = new bluebird_1.default((resolve, reject) => {
367
- this.asyncPromise = { resolve, reject };
368
- });
369
- await ( /** @type {RemoteDebugger} */(this.remote)).executeAtomAsync(atom, args, this.curWebFrames);
370
- return await this.waitForAtom(promise);
371
- },
372
- /**
373
- * @template {string} S
374
- * @param {S|Element<S>} elOrId
375
- * @returns {import('./types').AtomsElement<S>}
376
- * @this {XCUITestDriver}
377
- */
378
- getAtomsElement(elOrId) {
379
- const elId = support_1.util.unwrapElement(elOrId);
380
- if (!this.webElementsCache?.has(elId)) {
381
- throw new driver_1.errors.StaleElementReferenceError();
382
- }
383
- return { ELEMENT: this.webElementsCache.get(elId) };
384
- },
385
- /**
386
- * @param {readonly any[]} [args]
387
- * @this {XCUITestDriver}
388
- */
389
- convertElementsForAtoms(args = []) {
390
- return args.map((arg) => {
391
- if (helpers.hasElementId(arg)) {
392
- try {
393
- return this.getAtomsElement(arg);
394
- }
395
- catch (err) {
396
- if (!(0, driver_1.isErrorType)(err, driver_1.errors.StaleElementReferenceError)) {
397
- throw err;
398
- }
399
- }
400
- return arg;
401
- }
402
- return lodash_1.default.isArray(arg) ? this.convertElementsForAtoms(arg) : arg;
403
- });
404
- },
405
- getElementId(element) {
406
- return element.ELEMENT || element[W3C_WEB_ELEMENT_IDENTIFIER];
407
- },
408
- /**
409
- * @param {any} element
410
- * @returns {element is Element}
411
- */
412
- hasElementId(element) {
413
- return (support_1.util.hasValue(element) &&
414
- (support_1.util.hasValue(element.ELEMENT) || support_1.util.hasValue(element[W3C_WEB_ELEMENT_IDENTIFIER])));
415
- },
416
- };
417
- const extensions = {
418
- /**
419
- * @this {XCUITestDriver}
420
- */
421
- async findWebElementOrElements(strategy, selector, many, ctx) {
422
- const contextElement = lodash_1.default.isNil(ctx) ? null : this.getAtomsElement(ctx);
423
- const atomName = many ? 'find_elements' : 'find_element_fragment';
424
- let element;
425
- let doFind = async () => {
426
- element = await this.executeAtom(atomName, [strategy, selector, contextElement]);
427
- return !lodash_1.default.isNull(element);
428
- };
429
- try {
430
- await this.implicitWaitForCondition(doFind);
431
- }
432
- catch (err) {
433
- if (err.message && lodash_1.default.isFunction(err.message.match) && err.message.match(/Condition unmet/)) {
434
- // condition was not met setting res to empty array
435
- element = [];
225
+ return cookie;
226
+ });
227
+ }
228
+ /**
229
+ * @this {XCUITestDriver}
230
+ * @group Mobile Web Only
231
+ */
232
+ async function setCookie(cookie) {
233
+ if (!this.isWebContext()) {
234
+ throw new driver_1.errors.NotImplementedError();
235
+ }
236
+ cookie = lodash_1.default.clone(cookie);
237
+ // if `path` field is not specified, Safari will not update cookies as expected; eg issue #1708
238
+ if (!cookie.path) {
239
+ cookie.path = '/';
240
+ }
241
+ const jsCookie = cookieUtils.createJSCookie(cookie.name, cookie.value, {
242
+ expires: lodash_1.default.isNumber(cookie.expiry)
243
+ ? new Date(cookie.expiry * 1000).toUTCString()
244
+ : cookie.expiry,
245
+ path: cookie.path,
246
+ domain: cookie.domain,
247
+ httpOnly: cookie.httpOnly,
248
+ secure: cookie.secure,
249
+ });
250
+ let script = `document.cookie = ${JSON.stringify(jsCookie)}`;
251
+ await this.executeAtom('execute_script', [script, []]);
252
+ }
253
+ /**
254
+ * @this {XCUITestDriver}
255
+ * @group Mobile Web Only
256
+ */
257
+ async function deleteCookie(cookieName) {
258
+ if (!this.isWebContext()) {
259
+ throw new driver_1.errors.NotImplementedError();
260
+ }
261
+ const cookies = await this.getCookies();
262
+ const cookie = cookies.find(({ name }) => name === cookieName);
263
+ if (!cookie) {
264
+ this.log.debug(`Cookie '${cookieName}' not found. Ignoring.`);
265
+ return;
266
+ }
267
+ await this._deleteCookie(cookie);
268
+ }
269
+ /**
270
+ * @this {XCUITestDriver}
271
+ * @group Mobile Web Only
272
+ */
273
+ async function deleteCookies() {
274
+ if (!this.isWebContext()) {
275
+ throw new driver_1.errors.NotImplementedError();
276
+ }
277
+ const cookies = await this.getCookies();
278
+ await bluebird_1.default.all(cookies.map((cookie) => this._deleteCookie(cookie)));
279
+ }
280
+ /**
281
+ * @this {XCUITestDriver}
282
+ */
283
+ async function _deleteCookie(cookie) {
284
+ const url = `http${cookie.secure ? 's' : ''}://${cookie.domain}${cookie.path}`;
285
+ return await ( /** @type {RemoteDebugger} */(this.remote)).deleteCookie(cookie.name, url);
286
+ }
287
+ /**
288
+ * @this {XCUITestDriver}
289
+ */
290
+ function cacheWebElement(el) {
291
+ if (!lodash_1.default.isPlainObject(el)) {
292
+ return el;
293
+ }
294
+ const elId = support_1.util.unwrapElement(el);
295
+ if (!isValidElementIdentifier(elId)) {
296
+ return el;
297
+ }
298
+ // In newer debugger releases element identifiers look like `:wdc:1628151649325`
299
+ // We assume it is safe to use these to identify cached elements
300
+ const cacheId = lodash_1.default.includes(elId, ':') ? elId : support_1.util.uuidV4();
301
+ this.webElementsCache.set(cacheId, elId);
302
+ return support_1.util.wrapElement(cacheId);
303
+ }
304
+ /**
305
+ * @this {XCUITestDriver}
306
+ */
307
+ function cacheWebElements(response) {
308
+ const toCached = (v) => (lodash_1.default.isArray(v) || lodash_1.default.isPlainObject(v) ? this.cacheWebElements(v) : v);
309
+ if (lodash_1.default.isArray(response)) {
310
+ return response.map(toCached);
311
+ }
312
+ else if (lodash_1.default.isPlainObject(response)) {
313
+ const result = { ...response, ...this.cacheWebElement(response) };
314
+ return lodash_1.default.toPairs(result).reduce((acc, [key, value]) => {
315
+ acc[key] = toCached(value);
316
+ return acc;
317
+ }, {});
318
+ }
319
+ return response;
320
+ }
321
+ /**
322
+ * @param {string} atom
323
+ * @param {unknown[]} args
324
+ * @returns {Promise<any>}
325
+ * @privateRemarks This should return `Promise<T>` where `T` extends `unknown`, but that's going to cause a lot of things to break.
326
+ * @this {XCUITestDriver}
327
+ */
328
+ async function executeAtom(atom, args, alwaysDefaultFrame = false) {
329
+ let frames = alwaysDefaultFrame === true ? [] : this.curWebFrames;
330
+ let promise = ( /** @type {RemoteDebugger} */(this.remote)).executeAtom(atom, args, frames);
331
+ return await this.waitForAtom(promise);
332
+ }
333
+ /**
334
+ * @this {XCUITestDriver}
335
+ * @param {string} atom
336
+ * @param {any[]} args
337
+ */
338
+ async function executeAtomAsync(atom, args) {
339
+ // save the resolve and reject methods of the promise to be waited for
340
+ let promise = new bluebird_1.default((resolve, reject) => {
341
+ this.asyncPromise = { resolve, reject };
342
+ });
343
+ await ( /** @type {RemoteDebugger} */(this.remote)).executeAtomAsync(atom, args, this.curWebFrames);
344
+ return await this.waitForAtom(promise);
345
+ }
346
+ /**
347
+ * @template {string} S
348
+ * @param {S|Element<S>} elOrId
349
+ * @returns {import('./types').AtomsElement<S>}
350
+ * @this {XCUITestDriver}
351
+ */
352
+ function getAtomsElement(elOrId) {
353
+ const elId = support_1.util.unwrapElement(elOrId);
354
+ if (!this.webElementsCache?.has(elId)) {
355
+ throw new driver_1.errors.StaleElementReferenceError();
356
+ }
357
+ return { ELEMENT: this.webElementsCache.get(elId) };
358
+ }
359
+ /**
360
+ * @param {readonly any[]} [args]
361
+ * @this {XCUITestDriver}
362
+ */
363
+ function convertElementsForAtoms(args = []) {
364
+ return args.map((arg) => {
365
+ if (hasElementId(arg)) {
366
+ try {
367
+ return this.getAtomsElement(arg);
436
368
  }
437
- else {
438
- throw err;
369
+ catch (err) {
370
+ if (!(0, driver_1.isErrorType)(err, driver_1.errors.StaleElementReferenceError)) {
371
+ throw err;
372
+ }
439
373
  }
374
+ return arg;
440
375
  }
441
- if (many) {
442
- return this.cacheWebElements(element);
376
+ return lodash_1.default.isArray(arg) ? this.convertElementsForAtoms(arg) : arg;
377
+ });
378
+ }
379
+ function getElementId(element) {
380
+ return element.ELEMENT || element[W3C_WEB_ELEMENT_IDENTIFIER];
381
+ }
382
+ /**
383
+ * @param {any} element
384
+ * @returns {element is Element}
385
+ */
386
+ function hasElementId(element) {
387
+ return (support_1.util.hasValue(element) &&
388
+ (support_1.util.hasValue(element.ELEMENT) || support_1.util.hasValue(element[W3C_WEB_ELEMENT_IDENTIFIER])));
389
+ }
390
+ /**
391
+ * @this {XCUITestDriver}
392
+ */
393
+ async function findWebElementOrElements(strategy, selector, many, ctx) {
394
+ const contextElement = lodash_1.default.isNil(ctx) ? null : this.getAtomsElement(ctx);
395
+ const atomName = many ? 'find_elements' : 'find_element_fragment';
396
+ let element;
397
+ let doFind = async () => {
398
+ element = await this.executeAtom(atomName, [strategy, selector, contextElement]);
399
+ return !lodash_1.default.isNull(element);
400
+ };
401
+ try {
402
+ await this.implicitWaitForCondition(doFind);
403
+ }
404
+ catch (err) {
405
+ if (err.message && lodash_1.default.isFunction(err.message.match) && err.message.match(/Condition unmet/)) {
406
+ // condition was not met setting res to empty array
407
+ element = [];
443
408
  }
444
- if (lodash_1.default.isEmpty(element)) {
445
- throw new driver_1.errors.NoSuchElementError();
409
+ else {
410
+ throw err;
446
411
  }
412
+ }
413
+ if (many) {
447
414
  return this.cacheWebElements(element);
448
- },
449
- /**
450
- * @this {XCUITestDriver}
451
- * @param {number} x
452
- * @param {number} y
453
- */
454
- async clickWebCoords(x, y) {
455
- const { x: translatedX, y: translatedY } = await this.translateWebCoords(x, y);
456
- await this.mobileTap(translatedX, translatedY);
457
- },
458
- /**
459
- * @this {XCUITestDriver}
460
- * @returns {Promise<boolean>}
461
- */
462
- async getSafariIsIphone() {
463
- if (lodash_1.default.isBoolean(this._isSafariIphone)) {
464
- return this._isSafariIphone;
465
- }
466
- try {
467
- const userAgent = /** @type {string} */ (await this.execute('return navigator.userAgent'));
468
- this._isSafariIphone = userAgent.toLowerCase().includes('iphone');
469
- }
470
- catch (err) {
471
- this.log.warn(`Unable to find device type from useragent. Assuming iPhone`);
472
- this.log.debug(`Error: ${err.message}`);
473
- }
474
- return this._isSafariIphone ?? true;
475
- },
476
- /**
477
- * @this {XCUITestDriver}
478
- * @returns {Promise<import('@appium/types').Size>}
479
- */
480
- async getSafariDeviceSize() {
481
- const script = 'return {height: window.screen.availHeight * window.devicePixelRatio, width: window.screen.availWidth * window.devicePixelRatio};';
482
- const { width, height } = /** @type {import('@appium/types').Size} */ (await this.execute(script));
483
- const [normHeight, normWidth] = height > width ? [height, width] : [width, height];
484
- return {
485
- width: normWidth,
486
- height: normHeight,
487
- };
488
- },
489
- /**
490
- * @this {XCUITestDriver}
491
- * @returns {Promise<boolean>}
492
- */
493
- async getSafariIsNotched() {
494
- if (lodash_1.default.isBoolean(this._isSafariNotched)) {
495
- return this._isSafariNotched;
496
- }
497
- try {
498
- const { width, height } = await this.getSafariDeviceSize();
499
- for (const device of NOTCHED_DEVICE_SIZES) {
500
- if (device.w === width && device.h === height) {
501
- this._isSafariNotched = true;
502
- }
415
+ }
416
+ if (lodash_1.default.isEmpty(element)) {
417
+ throw new driver_1.errors.NoSuchElementError();
418
+ }
419
+ return this.cacheWebElements(element);
420
+ }
421
+ /**
422
+ * @this {XCUITestDriver}
423
+ * @param {number} x
424
+ * @param {number} y
425
+ */
426
+ async function clickWebCoords(x, y) {
427
+ const { x: translatedX, y: translatedY } = await this.translateWebCoords(x, y);
428
+ await this.mobileTap(translatedX, translatedY);
429
+ }
430
+ /**
431
+ * @this {XCUITestDriver}
432
+ * @returns {Promise<boolean>}
433
+ */
434
+ async function getSafariIsIphone() {
435
+ if (lodash_1.default.isBoolean(this._isSafariIphone)) {
436
+ return this._isSafariIphone;
437
+ }
438
+ try {
439
+ const userAgent = /** @type {string} */ (await this.execute('return navigator.userAgent'));
440
+ this._isSafariIphone = userAgent.toLowerCase().includes('iphone');
441
+ }
442
+ catch (err) {
443
+ this.log.warn(`Unable to find device type from useragent. Assuming iPhone`);
444
+ this.log.debug(`Error: ${err.message}`);
445
+ }
446
+ return this._isSafariIphone ?? true;
447
+ }
448
+ /**
449
+ * @this {XCUITestDriver}
450
+ * @returns {Promise<import('@appium/types').Size>}
451
+ */
452
+ async function getSafariDeviceSize() {
453
+ const script = 'return {height: window.screen.availHeight * window.devicePixelRatio, width: window.screen.availWidth * window.devicePixelRatio};';
454
+ const { width, height } = /** @type {import('@appium/types').Size} */ (await this.execute(script));
455
+ const [normHeight, normWidth] = height > width ? [height, width] : [width, height];
456
+ return {
457
+ width: normWidth,
458
+ height: normHeight,
459
+ };
460
+ }
461
+ /**
462
+ * @this {XCUITestDriver}
463
+ * @returns {Promise<boolean>}
464
+ */
465
+ async function getSafariIsNotched() {
466
+ if (lodash_1.default.isBoolean(this._isSafariNotched)) {
467
+ return this._isSafariNotched;
468
+ }
469
+ try {
470
+ const { width, height } = await this.getSafariDeviceSize();
471
+ for (const device of NOTCHED_DEVICE_SIZES) {
472
+ if (device.w === width && device.h === height) {
473
+ this._isSafariNotched = true;
503
474
  }
504
475
  }
505
- catch (err) {
506
- this.log.warn(`Unable to find device type from dimensions. Assuming the device is not notched`);
507
- this.log.debug(`Error: ${err.message}`);
508
- }
509
- return this._isSafariNotched ?? false;
510
- },
511
- /**
512
- * @this {XCUITestDriver}
513
- */
514
- async getExtraTranslateWebCoordsOffset(wvPos, realDims) {
515
- let topOffset = 0;
516
- let bottomOffset = 0;
517
- const isIphone = await this.getSafariIsIphone();
518
- // No need to check whether the Smart App Banner or Tab Bar is visible or not
519
- // if already defined by nativeWebTapTabBarVisibility or nativeWebTapSmartAppBannerVisibility in settings.
520
- const { nativeWebTapTabBarVisibility, nativeWebTapSmartAppBannerVisibility, safariTabBarPosition = support_1.util.compareVersions(/** @type {string} */ (this.opts.platformVersion), '>=', '15.0') &&
521
- isIphone
522
- ? TAB_BAR_POSITION_BOTTOM
523
- : TAB_BAR_POSITION_TOP, } = this.settings.getSettings();
524
- let tabBarVisibility = lodash_1.default.lowerCase(String(nativeWebTapTabBarVisibility));
525
- let bannerVisibility = lodash_1.default.lowerCase(String(nativeWebTapSmartAppBannerVisibility));
526
- const tabBarPosition = lodash_1.default.lowerCase(String(safariTabBarPosition));
527
- if (!VISIBILITIES.includes(tabBarVisibility)) {
528
- tabBarVisibility = DETECT;
529
- }
530
- if (!VISIBILITIES.includes(bannerVisibility)) {
531
- bannerVisibility = DETECT;
532
- }
533
- if (!TAB_BAR_POSSITIONS.includes(tabBarPosition)) {
534
- throw new driver_1.errors.InvalidArgumentError(`${safariTabBarPosition} is invalid as Safari tab bar position. Available positions are ${TAB_BAR_POSSITIONS}.`);
476
+ }
477
+ catch (err) {
478
+ this.log.warn(`Unable to find device type from dimensions. Assuming the device is not notched`);
479
+ this.log.debug(`Error: ${err.message}`);
480
+ }
481
+ return this._isSafariNotched ?? false;
482
+ }
483
+ /**
484
+ * @this {XCUITestDriver}
485
+ */
486
+ async function getExtraTranslateWebCoordsOffset(wvPos, realDims) {
487
+ let topOffset = 0;
488
+ let bottomOffset = 0;
489
+ const isIphone = await this.getSafariIsIphone();
490
+ // No need to check whether the Smart App Banner or Tab Bar is visible or not
491
+ // if already defined by nativeWebTapTabBarVisibility or nativeWebTapSmartAppBannerVisibility in settings.
492
+ const { nativeWebTapTabBarVisibility, nativeWebTapSmartAppBannerVisibility, safariTabBarPosition = support_1.util.compareVersions(/** @type {string} */ (this.opts.platformVersion), '>=', '15.0') &&
493
+ isIphone
494
+ ? TAB_BAR_POSITION_BOTTOM
495
+ : TAB_BAR_POSITION_TOP, } = this.settings.getSettings();
496
+ let tabBarVisibility = lodash_1.default.lowerCase(String(nativeWebTapTabBarVisibility));
497
+ let bannerVisibility = lodash_1.default.lowerCase(String(nativeWebTapSmartAppBannerVisibility));
498
+ const tabBarPosition = lodash_1.default.lowerCase(String(safariTabBarPosition));
499
+ if (!VISIBILITIES.includes(tabBarVisibility)) {
500
+ tabBarVisibility = DETECT;
501
+ }
502
+ if (!VISIBILITIES.includes(bannerVisibility)) {
503
+ bannerVisibility = DETECT;
504
+ }
505
+ if (!TAB_BAR_POSSITIONS.includes(tabBarPosition)) {
506
+ throw new driver_1.errors.InvalidArgumentError(`${safariTabBarPosition} is invalid as Safari tab bar position. Available positions are ${TAB_BAR_POSSITIONS}.`);
507
+ }
508
+ const isNotched = isIphone && (await this.getSafariIsNotched());
509
+ const orientation = realDims.h > realDims.w ? 'PORTRAIT' : 'LANDSCAPE';
510
+ const notchOffset = isNotched
511
+ ? support_1.util.compareVersions(/** @type {string} */ (this.opts.platformVersion), '=', '13.0')
512
+ ? IPHONE_X_NOTCH_OFFSET_IOS_13
513
+ : IPHONE_X_NOTCH_OFFSET_IOS
514
+ : 0;
515
+ const isScrolled = await this.execute('return document.documentElement.scrollTop > 0');
516
+ if (isScrolled) {
517
+ topOffset = IPHONE_SCROLLED_TOP_BAR_HEIGHT + notchOffset;
518
+ if (isNotched) {
519
+ topOffset -= IPHONE_X_SCROLLED_OFFSET;
520
+ }
521
+ // If the iPhone is landscape then there is no top bar
522
+ if (orientation === 'LANDSCAPE' && isIphone) {
523
+ topOffset = 0;
535
524
  }
536
- const isNotched = isIphone && (await this.getSafariIsNotched());
537
- const orientation = realDims.h > realDims.w ? 'PORTRAIT' : 'LANDSCAPE';
538
- const notchOffset = isNotched
539
- ? support_1.util.compareVersions(/** @type {string} */ (this.opts.platformVersion), '=', '13.0')
540
- ? IPHONE_X_NOTCH_OFFSET_IOS_13
541
- : IPHONE_X_NOTCH_OFFSET_IOS
542
- : 0;
543
- const isScrolled = await this.execute('return document.documentElement.scrollTop > 0');
544
- if (isScrolled) {
545
- topOffset = IPHONE_SCROLLED_TOP_BAR_HEIGHT + notchOffset;
546
- if (isNotched) {
547
- topOffset -= IPHONE_X_SCROLLED_OFFSET;
525
+ }
526
+ else {
527
+ topOffset = tabBarPosition === TAB_BAR_POSITION_BOTTOM ? 0 : IPHONE_TOP_BAR_HEIGHT;
528
+ topOffset += notchOffset;
529
+ this.log.debug(`tabBarPosition and topOffset: ${tabBarPosition}, ${topOffset}`);
530
+ if (isIphone) {
531
+ if (orientation === 'PORTRAIT') {
532
+ // The bottom bar is only visible when portrait
533
+ bottomOffset = IPHONE_BOTTOM_BAR_OFFSET;
548
534
  }
549
- // If the iPhone is landscape then there is no top bar
550
- if (orientation === 'LANDSCAPE' && isIphone) {
551
- topOffset = 0;
535
+ else {
536
+ topOffset = IPHONE_LANDSCAPE_TOP_BAR_HEIGHT;
552
537
  }
553
538
  }
554
- else {
555
- topOffset = tabBarPosition === TAB_BAR_POSITION_BOTTOM ? 0 : IPHONE_TOP_BAR_HEIGHT;
556
- topOffset += notchOffset;
557
- this.log.debug(`tabBarPosition and topOffset: ${tabBarPosition}, ${topOffset}`);
558
- if (isIphone) {
559
- if (orientation === 'PORTRAIT') {
560
- // The bottom bar is only visible when portrait
561
- bottomOffset = IPHONE_BOTTOM_BAR_OFFSET;
562
- }
563
- else {
564
- topOffset = IPHONE_LANDSCAPE_TOP_BAR_HEIGHT;
565
- }
539
+ if (orientation === 'LANDSCAPE' || !isIphone) {
540
+ if (tabBarVisibility === VISIBLE) {
541
+ topOffset += TAB_BAR_OFFSET;
566
542
  }
567
- if (orientation === 'LANDSCAPE' || !isIphone) {
568
- if (tabBarVisibility === VISIBLE) {
543
+ else if (tabBarVisibility === DETECT) {
544
+ // Tabs only appear if the device is landscape or if it's an iPad so we only check visibility in this case
545
+ // Assume that each tab bar is a WebView
546
+ const contextsAndViews = await this.getContextsAndViews();
547
+ const tabs = contextsAndViews.filter((ctx) => ctx.id.startsWith('WEBVIEW_'));
548
+ if (tabs.length > 1) {
549
+ this.log.debug(`Found ${tabs.length} tabs. Assuming the tab bar is visible`);
569
550
  topOffset += TAB_BAR_OFFSET;
570
551
  }
571
- else if (tabBarVisibility === DETECT) {
572
- // Tabs only appear if the device is landscape or if it's an iPad so we only check visibility in this case
573
- // Assume that each tab bar is a WebView
574
- const contextsAndViews = await this.getContextsAndViews();
575
- const tabs = contextsAndViews.filter((ctx) => ctx.id.startsWith('WEBVIEW_'));
576
- if (tabs.length > 1) {
577
- this.log.debug(`Found ${tabs.length} tabs. Assuming the tab bar is visible`);
578
- topOffset += TAB_BAR_OFFSET;
579
- }
580
- }
581
552
  }
582
553
  }
583
- topOffset += await this.getExtraNativeWebTapOffset(isIphone, bannerVisibility);
584
- wvPos.y += topOffset;
585
- realDims.h -= topOffset + bottomOffset;
586
- },
587
- /**
588
- * @this {XCUITestDriver}
589
- * @param {boolean} isIphone
590
- * @param {string} bannerVisibility
591
- * @returns {Promise<number>}
592
- */
593
- async getExtraNativeWebTapOffset(isIphone, bannerVisibility) {
594
- let offset = 0;
595
- if (bannerVisibility === VISIBLE) {
554
+ }
555
+ topOffset += await this.getExtraNativeWebTapOffset(isIphone, bannerVisibility);
556
+ wvPos.y += topOffset;
557
+ realDims.h -= topOffset + bottomOffset;
558
+ }
559
+ /**
560
+ * @this {XCUITestDriver}
561
+ * @param {boolean} isIphone
562
+ * @param {string} bannerVisibility
563
+ * @returns {Promise<number>}
564
+ */
565
+ async function getExtraNativeWebTapOffset(isIphone, bannerVisibility) {
566
+ let offset = 0;
567
+ if (bannerVisibility === VISIBLE) {
568
+ offset += isIphone
569
+ ? IPHONE_WEB_COORD_SMART_APP_BANNER_OFFSET
570
+ : IPAD_WEB_COORD_SMART_APP_BANNER_OFFSET;
571
+ }
572
+ else if (bannerVisibility === DETECT) {
573
+ // try to see if there is an Smart App Banner
574
+ const banners = /** @type {import('@appium/types').Element[]} */ (await this.findNativeElementOrElements('accessibility id', 'Close app download offer', true));
575
+ if (banners?.length) {
596
576
  offset += isIphone
597
577
  ? IPHONE_WEB_COORD_SMART_APP_BANNER_OFFSET
598
578
  : IPAD_WEB_COORD_SMART_APP_BANNER_OFFSET;
599
579
  }
600
- else if (bannerVisibility === DETECT) {
601
- // try to see if there is an Smart App Banner
602
- const banners = /** @type {import('@appium/types').Element[]} */ (await this.findNativeElementOrElements('accessibility id', 'Close app download offer', true));
603
- if (banners?.length) {
604
- offset += isIphone
605
- ? IPHONE_WEB_COORD_SMART_APP_BANNER_OFFSET
606
- : IPAD_WEB_COORD_SMART_APP_BANNER_OFFSET;
607
- }
608
- }
609
- this.log.debug(`Additional native web tap offset computed: ${offset}`);
610
- return offset;
611
- },
612
- /**
613
- * @this {XCUITestDriver}
614
- * @param {any} el
615
- * @returns {Promise<void>}
616
- */
617
- async nativeWebTap(el) {
618
- const atomsElement = this.getAtomsElement(el);
619
- // if strict native tap, do not try to do it with WDA directly
620
- if (!(this.settings.getSettings()).nativeWebTapStrict &&
621
- (await tapWebElementNatively.bind(this)(atomsElement))) {
622
- return;
623
- }
624
- this.log.warn('Unable to do simple native web tap. Attempting to convert coordinates');
625
- const [size, coordinates] =
626
- /** @type {[import('@appium/types').Size, import('@appium/types').Position]} */ (await bluebird_1.default.Promise.all([
627
- this.executeAtom('get_size', [atomsElement]),
628
- this.executeAtom('get_top_left_coordinates', [atomsElement]),
629
- ]));
630
- const { width, height } = size;
631
- const { x, y } = coordinates;
632
- await this.clickWebCoords(x + width / 2, y + height / 2);
633
- },
634
- /**
635
- * @this {XCUITestDriver}
636
- * @param {number} x
637
- * @param {number} y
638
- * @returns {Promise<import('@appium/types').Position>}
639
- */
640
- async translateWebCoords(x, y) {
641
- this.log.debug(`Translating web coordinates (${JSON.stringify({ x, y })}) to native coordinates`);
642
- if (this.webviewCalibrationResult) {
643
- this.log.debug(`Will use the recent calibration result: ${JSON.stringify(this.webviewCalibrationResult)}`);
644
- const { offsetX, offsetY, pixelRatioX, pixelRatioY } = this.webviewCalibrationResult;
645
- const cmd = '(function () {return {innerWidth: window.innerWidth, innerHeight: window.innerHeight, ' +
646
- 'outerWidth: window.outerWidth, outerHeight: window.outerHeight}; })()';
647
- const wvDims = await ( /** @type {RemoteDebugger} */(this.remote)).execute(cmd);
648
- // https://tripleodeon.com/2011/12/first-understand-your-screen/
649
- const shouldApplyPixelRatio = wvDims.innerWidth > wvDims.outerWidth
650
- || wvDims.innerHeight > wvDims.outerHeight;
651
- return {
652
- x: offsetX + x * (shouldApplyPixelRatio ? pixelRatioX : 1),
653
- y: offsetY + y * (shouldApplyPixelRatio ? pixelRatioY : 1),
654
- };
655
- }
656
- else {
657
- this.log.debug(`Using the legacy algorithm for coordinates translation. ` +
658
- `Invoke 'mobile: calibrateWebToRealCoordinatesTranslation' to change that.`);
659
- }
660
- // absolutize web coords
661
- /** @type {import('@appium/types').Element|undefined|string} */
662
- let webview;
663
- try {
664
- webview = /** @type {import('@appium/types').Element|undefined} */ (await (0, asyncbox_1.retryInterval)(5, 100, async () => await this.findNativeElementOrElements('class name', 'XCUIElementTypeWebView', false)));
665
- }
666
- catch { }
667
- if (!webview) {
668
- throw new Error(`No WebView found. Unable to translate web coordinates for native web tap.`);
669
- }
670
- webview = support_1.util.unwrapElement(webview);
671
- const rect = /** @type {Rect} */ (await this.proxyCommand(`/element/${webview}/rect`, 'GET'));
672
- const wvPos = { x: rect.x, y: rect.y };
673
- const realDims = { w: rect.width, h: rect.height };
674
- const cmd = '(function () { return {w: window.innerWidth, h: window.innerHeight}; })()';
580
+ }
581
+ this.log.debug(`Additional native web tap offset computed: ${offset}`);
582
+ return offset;
583
+ }
584
+ /**
585
+ * @this {XCUITestDriver}
586
+ * @param {any} el
587
+ * @returns {Promise<void>}
588
+ */
589
+ async function nativeWebTap(el) {
590
+ const atomsElement = this.getAtomsElement(el);
591
+ // if strict native tap, do not try to do it with WDA directly
592
+ if (!(this.settings.getSettings()).nativeWebTapStrict &&
593
+ (await tapWebElementNatively.bind(this)(atomsElement))) {
594
+ return;
595
+ }
596
+ this.log.warn('Unable to do simple native web tap. Attempting to convert coordinates');
597
+ const [size, coordinates] =
598
+ /** @type {[import('@appium/types').Size, import('@appium/types').Position]} */ (await bluebird_1.default.Promise.all([
599
+ this.executeAtom('get_size', [atomsElement]),
600
+ this.executeAtom('get_top_left_coordinates', [atomsElement]),
601
+ ]));
602
+ const { width, height } = size;
603
+ const { x, y } = coordinates;
604
+ await this.clickWebCoords(x + width / 2, y + height / 2);
605
+ }
606
+ /**
607
+ * @this {XCUITestDriver}
608
+ * @param {number} x
609
+ * @param {number} y
610
+ * @returns {Promise<import('@appium/types').Position>}
611
+ */
612
+ async function translateWebCoords(x, y) {
613
+ this.log.debug(`Translating web coordinates (${JSON.stringify({ x, y })}) to native coordinates`);
614
+ if (this.webviewCalibrationResult) {
615
+ this.log.debug(`Will use the recent calibration result: ${JSON.stringify(this.webviewCalibrationResult)}`);
616
+ const { offsetX, offsetY, pixelRatioX, pixelRatioY } = this.webviewCalibrationResult;
617
+ const cmd = '(function () {return {innerWidth: window.innerWidth, innerHeight: window.innerHeight, ' +
618
+ 'outerWidth: window.outerWidth, outerHeight: window.outerHeight}; })()';
675
619
  const wvDims = await ( /** @type {RemoteDebugger} */(this.remote)).execute(cmd);
676
- // keep track of implicit wait, and set locally to 0
677
- // https://github.com/appium/appium/issues/14988
678
- const implicitWaitMs = this.implicitWaitMs;
679
- this.setImplicitWait(0);
620
+ // https://tripleodeon.com/2011/12/first-understand-your-screen/
621
+ const shouldApplyPixelRatio = wvDims.innerWidth > wvDims.outerWidth
622
+ || wvDims.innerHeight > wvDims.outerHeight;
623
+ return {
624
+ x: offsetX + x * (shouldApplyPixelRatio ? pixelRatioX : 1),
625
+ y: offsetY + y * (shouldApplyPixelRatio ? pixelRatioY : 1),
626
+ };
627
+ }
628
+ else {
629
+ this.log.debug(`Using the legacy algorithm for coordinates translation. ` +
630
+ `Invoke 'mobile: calibrateWebToRealCoordinatesTranslation' to change that.`);
631
+ }
632
+ // absolutize web coords
633
+ /** @type {import('@appium/types').Element|undefined|string} */
634
+ let webview;
635
+ try {
636
+ webview = /** @type {import('@appium/types').Element|undefined} */ (await (0, asyncbox_1.retryInterval)(5, 100, async () => await this.findNativeElementOrElements('class name', 'XCUIElementTypeWebView', false)));
637
+ }
638
+ catch { }
639
+ if (!webview) {
640
+ throw new Error(`No WebView found. Unable to translate web coordinates for native web tap.`);
641
+ }
642
+ webview = support_1.util.unwrapElement(webview);
643
+ const rect = /** @type {Rect} */ (await this.proxyCommand(`/element/${webview}/rect`, 'GET'));
644
+ const wvPos = { x: rect.x, y: rect.y };
645
+ const realDims = { w: rect.width, h: rect.height };
646
+ const cmd = '(function () { return {w: window.innerWidth, h: window.innerHeight}; })()';
647
+ const wvDims = await ( /** @type {RemoteDebugger} */(this.remote)).execute(cmd);
648
+ // keep track of implicit wait, and set locally to 0
649
+ // https://github.com/appium/appium/issues/14988
650
+ const implicitWaitMs = this.implicitWaitMs;
651
+ this.setImplicitWait(0);
652
+ try {
653
+ await this.getExtraTranslateWebCoordsOffset(wvPos, realDims);
654
+ }
655
+ finally {
656
+ this.setImplicitWait(implicitWaitMs);
657
+ }
658
+ if (!wvDims || !realDims || !wvPos) {
659
+ throw new Error(`Web coordinates ${JSON.stringify({ x, y })} cannot be translated into real coordinates. ` +
660
+ `Try to invoke 'mobile: calibrateWebToRealCoordinatesTranslation' or consider translating the ` +
661
+ `coordinates from the client code.`);
662
+ }
663
+ const xRatio = realDims.w / wvDims.w;
664
+ const yRatio = realDims.h / wvDims.h;
665
+ const newCoords = {
666
+ x: wvPos.x + Math.round(xRatio * x),
667
+ y: wvPos.y + Math.round(yRatio * y),
668
+ };
669
+ // additional logging for coordinates, since it is sometimes broken
670
+ // see https://github.com/appium/appium/issues/9159
671
+ this.log.debug(`Converted coordinates: ${JSON.stringify(newCoords)}`);
672
+ this.log.debug(` rect: ${JSON.stringify(rect)}`);
673
+ this.log.debug(` wvPos: ${JSON.stringify(wvPos)}`);
674
+ this.log.debug(` realDims: ${JSON.stringify(realDims)}`);
675
+ this.log.debug(` wvDims: ${JSON.stringify(wvDims)}`);
676
+ this.log.debug(` xRatio: ${JSON.stringify(xRatio)}`);
677
+ this.log.debug(` yRatio: ${JSON.stringify(yRatio)}`);
678
+ this.log.debug(`Converted web coords ${JSON.stringify({ x, y })} into real coords ${JSON.stringify(newCoords)}`);
679
+ return newCoords;
680
+ }
681
+ /**
682
+ * @this {XCUITestDriver}
683
+ * @returns {Promise<boolean>}
684
+ */
685
+ async function checkForAlert() {
686
+ return lodash_1.default.isString(await this.getAlertText());
687
+ }
688
+ /**
689
+ * @param {Promise<any>} promise
690
+ * @this {XCUITestDriver}
691
+ */
692
+ async function waitForAtom(promise) {
693
+ const timer = new support_1.timing.Timer().start();
694
+ const atomWaitTimeoutMs = lodash_1.default.isNumber(this.opts.webviewAtomWaitTimeout) && this.opts.webviewAtomWaitTimeout > 0
695
+ ? this.opts.webviewAtomWaitTimeout
696
+ : ATOM_WAIT_TIMEOUT_MS;
697
+ // need to check for alert while the atom is being executed.
698
+ // so notify ourselves when it happens
699
+ const timedAtomPromise = bluebird_1.default.resolve(promise).timeout(atomWaitTimeoutMs);
700
+ const handlePromiseError = async (p) => {
680
701
  try {
681
- await this.getExtraTranslateWebCoordsOffset(wvPos, realDims);
682
- }
683
- finally {
684
- this.setImplicitWait(implicitWaitMs);
685
- }
686
- if (!wvDims || !realDims || !wvPos) {
687
- throw new Error(`Web coordinates ${JSON.stringify({ x, y })} cannot be translated into real coordinates. ` +
688
- `Try to invoke 'mobile: calibrateWebToRealCoordinatesTranslation' or consider translating the ` +
689
- `coordinates from the client code.`);
702
+ return await p;
690
703
  }
691
- const xRatio = realDims.w / wvDims.w;
692
- const yRatio = realDims.h / wvDims.h;
693
- const newCoords = {
694
- x: wvPos.x + Math.round(xRatio * x),
695
- y: wvPos.y + Math.round(yRatio * y),
696
- };
697
- // additional logging for coordinates, since it is sometimes broken
698
- // see https://github.com/appium/appium/issues/9159
699
- this.log.debug(`Converted coordinates: ${JSON.stringify(newCoords)}`);
700
- this.log.debug(` rect: ${JSON.stringify(rect)}`);
701
- this.log.debug(` wvPos: ${JSON.stringify(wvPos)}`);
702
- this.log.debug(` realDims: ${JSON.stringify(realDims)}`);
703
- this.log.debug(` wvDims: ${JSON.stringify(wvDims)}`);
704
- this.log.debug(` xRatio: ${JSON.stringify(xRatio)}`);
705
- this.log.debug(` yRatio: ${JSON.stringify(yRatio)}`);
706
- this.log.debug(`Converted web coords ${JSON.stringify({ x, y })} into real coords ${JSON.stringify(newCoords)}`);
707
- return newCoords;
708
- },
709
- /**
710
- * @this {XCUITestDriver}
711
- * @returns {Promise<boolean>}
712
- */
713
- async checkForAlert() {
714
- return lodash_1.default.isString(await this.getAlertText());
715
- },
716
- /**
717
- * @param {Promise<any>} promise
718
- * @this {XCUITestDriver}
719
- */
720
- async waitForAtom(promise) {
721
- const timer = new support_1.timing.Timer().start();
722
- const atomWaitTimeoutMs = lodash_1.default.isNumber(this.opts.webviewAtomWaitTimeout) && this.opts.webviewAtomWaitTimeout > 0
723
- ? this.opts.webviewAtomWaitTimeout
724
- : ATOM_WAIT_TIMEOUT_MS;
725
- // need to check for alert while the atom is being executed.
726
- // so notify ourselves when it happens
727
- const timedAtomPromise = bluebird_1.default.resolve(promise).timeout(atomWaitTimeoutMs);
728
- const handlePromiseError = async (p) => {
729
- try {
730
- return await p;
731
- }
732
- catch (err) {
733
- const originalError = err instanceof bluebird_1.AggregateError ? err[0] : err;
734
- this.log.debug(`Error received while executing atom: ${originalError.message}`);
735
- throw (originalError instanceof bluebird_1.TimeoutError
736
- ? (await generateAtomTimeoutError.bind(this)(timer))
737
- : originalError);
738
- }
739
- };
740
- // if the atom promise is fulfilled within ATOM_INITIAL_WAIT_MS
741
- // then we don't need to check for an alert presence
742
- await handlePromiseError(bluebird_1.default.any([bluebird_1.default.delay(ATOM_INITIAL_WAIT_MS), timedAtomPromise]));
743
- if (timedAtomPromise.isFulfilled()) {
744
- return await timedAtomPromise;
704
+ catch (err) {
705
+ const originalError = err instanceof bluebird_1.AggregateError ? err[0] : err;
706
+ this.log.debug(`Error received while executing atom: ${originalError.message}`);
707
+ throw (originalError instanceof bluebird_1.TimeoutError
708
+ ? (await generateAtomTimeoutError.bind(this)(timer))
709
+ : originalError);
745
710
  }
746
- // ...otherwise make sure there is no unexpected alert covering the element
747
- this._waitingAtoms.count++;
748
- let onAlertCallback;
749
- let onAppCrashCallback;
750
- try {
751
- // only restart the monitor if it is not running already
752
- if (this._waitingAtoms.alertMonitor.isResolved()) {
753
- this._waitingAtoms.alertMonitor = bluebird_1.default.resolve((async () => {
754
- while (this._waitingAtoms.count > 0) {
755
- try {
756
- if (await this.checkForAlert()) {
757
- this._waitingAtoms.alertNotifier.emit(ON_OBSTRUCTING_ALERT_EVENT);
758
- }
711
+ };
712
+ // if the atom promise is fulfilled within ATOM_INITIAL_WAIT_MS
713
+ // then we don't need to check for an alert presence
714
+ await handlePromiseError(bluebird_1.default.any([bluebird_1.default.delay(ATOM_INITIAL_WAIT_MS), timedAtomPromise]));
715
+ if (timedAtomPromise.isFulfilled()) {
716
+ return await timedAtomPromise;
717
+ }
718
+ // ...otherwise make sure there is no unexpected alert covering the element
719
+ this._waitingAtoms.count++;
720
+ let onAlertCallback;
721
+ let onAppCrashCallback;
722
+ try {
723
+ // only restart the monitor if it is not running already
724
+ if (this._waitingAtoms.alertMonitor.isResolved()) {
725
+ this._waitingAtoms.alertMonitor = bluebird_1.default.resolve((async () => {
726
+ while (this._waitingAtoms.count > 0) {
727
+ try {
728
+ if (await this.checkForAlert()) {
729
+ this._waitingAtoms.alertNotifier.emit(ON_OBSTRUCTING_ALERT_EVENT);
759
730
  }
760
- catch (err) {
761
- if ((0, driver_1.isErrorType)(err, driver_1.errors.InvalidElementStateError)) {
762
- this._waitingAtoms.alertNotifier.emit(ON_APP_CRASH_EVENT, err);
763
- }
731
+ }
732
+ catch (err) {
733
+ if ((0, driver_1.isErrorType)(err, driver_1.errors.InvalidElementStateError)) {
734
+ this._waitingAtoms.alertNotifier.emit(ON_APP_CRASH_EVENT, err);
764
735
  }
765
- await bluebird_1.default.delay(OBSTRUCTING_ALERT_PRESENCE_CHECK_INTERVAL_MS);
766
736
  }
767
- })());
768
- }
769
- return await new bluebird_1.default((resolve, reject) => {
770
- onAlertCallback = () => reject(new driver_1.errors.UnexpectedAlertOpenError());
771
- onAppCrashCallback = reject;
772
- this._waitingAtoms.alertNotifier.once(ON_OBSTRUCTING_ALERT_EVENT, onAlertCallback);
773
- this._waitingAtoms.alertNotifier.once(ON_APP_CRASH_EVENT, onAppCrashCallback);
774
- handlePromiseError(timedAtomPromise)
775
- .then(resolve)
776
- .catch(reject);
777
- });
737
+ await bluebird_1.default.delay(OBSTRUCTING_ALERT_PRESENCE_CHECK_INTERVAL_MS);
738
+ }
739
+ })());
740
+ }
741
+ return await new bluebird_1.default((resolve, reject) => {
742
+ onAlertCallback = () => reject(new driver_1.errors.UnexpectedAlertOpenError());
743
+ onAppCrashCallback = reject;
744
+ this._waitingAtoms.alertNotifier.once(ON_OBSTRUCTING_ALERT_EVENT, onAlertCallback);
745
+ this._waitingAtoms.alertNotifier.once(ON_APP_CRASH_EVENT, onAppCrashCallback);
746
+ handlePromiseError(timedAtomPromise)
747
+ .then(resolve)
748
+ .catch(reject);
749
+ });
750
+ }
751
+ finally {
752
+ if (onAlertCallback) {
753
+ this._waitingAtoms.alertNotifier.removeListener(ON_OBSTRUCTING_ALERT_EVENT, onAlertCallback);
778
754
  }
779
- finally {
780
- if (onAlertCallback) {
781
- this._waitingAtoms.alertNotifier.removeListener(ON_OBSTRUCTING_ALERT_EVENT, onAlertCallback);
782
- }
783
- if (onAppCrashCallback) {
784
- this._waitingAtoms.alertNotifier.removeListener(ON_APP_CRASH_EVENT, onAppCrashCallback);
785
- }
786
- this._waitingAtoms.count--;
755
+ if (onAppCrashCallback) {
756
+ this._waitingAtoms.alertNotifier.removeListener(ON_APP_CRASH_EVENT, onAppCrashCallback);
787
757
  }
788
- },
789
- /**
790
- * @param {string} navType
791
- * @this {XCUITestDriver}
792
- */
793
- async mobileWebNav(navType) {
794
- ( /** @type {RemoteDebugger} */(this.remote)).allowNavigationWithoutReload = true;
758
+ this._waitingAtoms.count--;
759
+ }
760
+ }
761
+ /**
762
+ * @param {string} navType
763
+ * @this {XCUITestDriver}
764
+ */
765
+ async function mobileWebNav(navType) {
766
+ ( /** @type {RemoteDebugger} */(this.remote)).allowNavigationWithoutReload = true;
767
+ try {
768
+ await this.executeAtom('execute_script', [`history.${navType}();`, null]);
769
+ }
770
+ finally {
771
+ ( /** @type {RemoteDebugger} */(this.remote)).allowNavigationWithoutReload = false;
772
+ }
773
+ }
774
+ /**
775
+ * @this {XCUITestDriver}
776
+ * @returns {string} The base url which could be used to access WDA HTTP endpoints
777
+ * FROM THE SAME DEVICE where WDA is running
778
+ */
779
+ function getWdaLocalhostRoot() {
780
+ const remotePort = ((this.isRealDevice() ? this.opts.wdaRemotePort : null)
781
+ ?? this.wda?.url?.port
782
+ ?? this.opts.wdaLocalPort)
783
+ || 8100;
784
+ return `http://127.0.0.1:${remotePort}`;
785
+ }
786
+ /**
787
+ * Calibrates web to real coordinates translation.
788
+ * This API can only be called from Safari web context.
789
+ * It must load a custom page to the browser, and then restore
790
+ * the original one, so don't call it if you can potentially
791
+ * lose the current web app state.
792
+ * The outcome of this API is then used in nativeWebTap mode.
793
+ * The returned value could also be used to manually transform web coordinates
794
+ * to real devices ones in client scripts.
795
+ *
796
+ * @this {XCUITestDriver}
797
+ * @returns {Promise<import('../types').CalibrationData>}
798
+ */
799
+ async function mobileCalibrateWebToRealCoordinatesTranslation() {
800
+ if (!this.isWebContext()) {
801
+ throw new driver_1.errors.NotImplementedError('This API can only be called from a web context');
802
+ }
803
+ const currentUrl = await this.getUrl();
804
+ await this.setUrl(`${this.getWdaLocalhostRoot()}/calibrate`);
805
+ const { width, height } = /** @type {import('@appium/types').Rect} */ (await this.proxyCommand('/window/rect', 'GET'));
806
+ const [centerX, centerY] = [width / 2, height / 2];
807
+ const errorPrefix = 'Cannot determine web view coordinates offset. Are you in Safari context?';
808
+ const performCalibrationTap = async (/** @type {number} */ tapX, /** @type {number} */ tapY) => {
809
+ await this.mobileTap(tapX, tapY);
810
+ /** @type {import('@appium/types').Position} */
811
+ let result;
795
812
  try {
796
- await this.executeAtom('execute_script', [`history.${navType}();`, null]);
797
- }
798
- finally {
799
- ( /** @type {RemoteDebugger} */(this.remote)).allowNavigationWithoutReload = false;
813
+ const title = await this.title();
814
+ this.log.debug(JSON.stringify(title));
815
+ result = lodash_1.default.isPlainObject(title) ? title : JSON.parse(title);
800
816
  }
801
- },
802
- /**
803
- * @this {XCUITestDriver}
804
- * @returns {string} The base url which could be used to access WDA HTTP endpoints
805
- * FROM THE SAME DEVICE where WDA is running
806
- */
807
- getWdaLocalhostRoot() {
808
- const remotePort = ((this.isRealDevice() ? this.opts.wdaRemotePort : null)
809
- ?? this.wda?.url?.port
810
- ?? this.opts.wdaLocalPort)
811
- || 8100;
812
- return `http://127.0.0.1:${remotePort}`;
813
- },
814
- /**
815
- * Calibrates web to real coordinates translation.
816
- * This API can only be called from Safari web context.
817
- * It must load a custom page to the browser, and then restore
818
- * the original one, so don't call it if you can potentially
819
- * lose the current web app state.
820
- * The outcome of this API is then used in nativeWebTap mode.
821
- * The returned value could also be used to manually transform web coordinates
822
- * to real devices ones in client scripts.
823
- *
824
- * @this {XCUITestDriver}
825
- * @returns {Promise<import('../types').CalibrationData>}
826
- */
827
- async mobileCalibrateWebToRealCoordinatesTranslation() {
828
- if (!this.isWebContext()) {
829
- throw new driver_1.errors.NotImplementedError('This API can only be called from a web context');
817
+ catch (e) {
818
+ throw new Error(`${errorPrefix} Original error: ${e.message}`);
830
819
  }
831
- const currentUrl = await this.getUrl();
832
- await this.setUrl(`${this.getWdaLocalhostRoot()}/calibrate`);
833
- const { width, height } = /** @type {import('@appium/types').Rect} */ (await this.proxyCommand('/window/rect', 'GET'));
834
- const [centerX, centerY] = [width / 2, height / 2];
835
- const errorPrefix = 'Cannot determine web view coordinates offset. Are you in Safari context?';
836
- const performCalibrationTap = async (/** @type {number} */ tapX, /** @type {number} */ tapY) => {
837
- await this.mobileTap(tapX, tapY);
838
- /** @type {import('@appium/types').Position} */
839
- let result;
840
- try {
841
- const title = await this.title();
842
- this.log.debug(JSON.stringify(title));
843
- result = lodash_1.default.isPlainObject(title) ? title : JSON.parse(title);
844
- }
845
- catch (e) {
846
- throw new Error(`${errorPrefix} Original error: ${e.message}`);
847
- }
848
- const { x, y } = result;
849
- if (!lodash_1.default.isInteger(x) || !lodash_1.default.isInteger(y)) {
850
- throw new Error(errorPrefix);
851
- }
852
- return result;
853
- };
854
- await (0, asyncbox_1.retryInterval)(6, 500, async () => {
855
- const { x: x0, y: y0 } = await performCalibrationTap(centerX - CALIBRATION_TAP_DELTA_PX, centerY - CALIBRATION_TAP_DELTA_PX);
856
- const { x: x1, y: y1 } = await performCalibrationTap(centerX + CALIBRATION_TAP_DELTA_PX, centerY + CALIBRATION_TAP_DELTA_PX);
857
- const pixelRatioX = CALIBRATION_TAP_DELTA_PX * 2 / (x1 - x0);
858
- const pixelRatioY = CALIBRATION_TAP_DELTA_PX * 2 / (y1 - y0);
859
- this.webviewCalibrationResult = {
860
- offsetX: centerX - CALIBRATION_TAP_DELTA_PX - x0 * pixelRatioX,
861
- offsetY: centerY - CALIBRATION_TAP_DELTA_PX - y0 * pixelRatioY,
862
- pixelRatioX,
863
- pixelRatioY,
864
- };
865
- });
866
- if (currentUrl) {
867
- // restore the previous url
868
- await this.setUrl(currentUrl);
820
+ const { x, y } = result;
821
+ if (!lodash_1.default.isInteger(x) || !lodash_1.default.isInteger(y)) {
822
+ throw new Error(errorPrefix);
869
823
  }
870
- const result = /** @type {import('../types').CalibrationData} */ (this.webviewCalibrationResult);
871
- return {
872
- ...result,
873
- offsetX: Math.round(result.offsetX),
874
- offsetY: Math.round(result.offsetY),
824
+ return result;
825
+ };
826
+ await (0, asyncbox_1.retryInterval)(6, 500, async () => {
827
+ const { x: x0, y: y0 } = await performCalibrationTap(centerX - CALIBRATION_TAP_DELTA_PX, centerY - CALIBRATION_TAP_DELTA_PX);
828
+ const { x: x1, y: y1 } = await performCalibrationTap(centerX + CALIBRATION_TAP_DELTA_PX, centerY + CALIBRATION_TAP_DELTA_PX);
829
+ const pixelRatioX = CALIBRATION_TAP_DELTA_PX * 2 / (x1 - x0);
830
+ const pixelRatioY = CALIBRATION_TAP_DELTA_PX * 2 / (y1 - y0);
831
+ this.webviewCalibrationResult = {
832
+ offsetX: centerX - CALIBRATION_TAP_DELTA_PX - x0 * pixelRatioX,
833
+ offsetY: centerY - CALIBRATION_TAP_DELTA_PX - y0 * pixelRatioY,
834
+ pixelRatioX,
835
+ pixelRatioY,
875
836
  };
876
- },
877
- /**
878
- * @typedef {Object} SafariOpts
879
- * @property {object} preferences An object containing Safari settings to be updated.
880
- * The list of available setting names and their values could be retrieved by
881
- * changing the corresponding Safari settings in the UI and then inspecting
882
- * 'Library/Preferences/com.apple.mobilesafari.plist' file inside of
883
- * com.apple.mobilesafari app container.
884
- * The full path to the Mobile Safari's container could be retrieved from
885
- * `xcrun simctl get_app_container <sim_udid> com.apple.mobilesafari data`
886
- * command output.
887
- * Use the `xcrun simctl spawn <sim_udid> defaults read <path_to_plist>` command
888
- * to print the plist content to the Terminal.
889
- */
890
- /**
891
- * Updates Mobile Safari preferences on an iOS Simulator
892
- *
893
- * @param {import('@appium/types').StringRecord} preferences - An object containing Safari settings to be updated.
894
- * The list of available setting names and their values can be retrieved by changing the
895
- * corresponding Safari settings in the UI and then inspecting
896
- * `Library/Preferences/com.apple.mobilesafari.plist` file inside of the `com.apple.mobilesafari`
897
- * app container within the simulator filesystem. The full path to Mobile Safari's container can
898
- * be retrieved by running `xcrun simctl get_app_container <sim_udid> com.apple.mobilesafari
899
- * data`. Use the `xcrun simctl spawn <sim_udid> defaults read <path_to_plist>` command to print
900
- * the plist content to the Terminal.
901
- *
902
- * @group Simulator Only
903
- * @returns {Promise<void>}
904
- * @throws {Error} if run on a real device or if the preferences argument is invalid
905
- * @this {XCUITestDriver}
906
- */
907
- async mobileUpdateSafariPreferences(preferences) {
908
- if (!this.isSimulator()) {
909
- throw new Error('This extension is only available for Simulator');
910
- }
911
- if (!lodash_1.default.isPlainObject(preferences)) {
912
- throw new driver_1.errors.InvalidArgumentError('"preferences" argument must be a valid object');
913
- }
914
- this.log.debug(`About to update Safari preferences: ${JSON.stringify(preferences)}`);
915
- await /** @type {import('../driver').Simulator} */ (this.device).updateSafariSettings(preferences);
916
- },
917
- };
918
- exports.default = { ...helpers, ...extensions, ...commands };
837
+ });
838
+ if (currentUrl) {
839
+ // restore the previous url
840
+ await this.setUrl(currentUrl);
841
+ }
842
+ const result = /** @type {import('../types').CalibrationData} */ (this.webviewCalibrationResult);
843
+ return {
844
+ ...result,
845
+ offsetX: Math.round(result.offsetX),
846
+ offsetY: Math.round(result.offsetY),
847
+ };
848
+ }
849
+ /**
850
+ * @typedef {Object} SafariOpts
851
+ * @property {object} preferences An object containing Safari settings to be updated.
852
+ * The list of available setting names and their values could be retrieved by
853
+ * changing the corresponding Safari settings in the UI and then inspecting
854
+ * 'Library/Preferences/com.apple.mobilesafari.plist' file inside of
855
+ * com.apple.mobilesafari app container.
856
+ * The full path to the Mobile Safari's container could be retrieved from
857
+ * `xcrun simctl get_app_container <sim_udid> com.apple.mobilesafari data`
858
+ * command output.
859
+ * Use the `xcrun simctl spawn <sim_udid> defaults read <path_to_plist>` command
860
+ * to print the plist content to the Terminal.
861
+ */
862
+ /**
863
+ * Updates Mobile Safari preferences on an iOS Simulator
864
+ *
865
+ * @param {import('@appium/types').StringRecord} preferences - An object containing Safari settings to be updated.
866
+ * The list of available setting names and their values can be retrieved by changing the
867
+ * corresponding Safari settings in the UI and then inspecting
868
+ * `Library/Preferences/com.apple.mobilesafari.plist` file inside of the `com.apple.mobilesafari`
869
+ * app container within the simulator filesystem. The full path to Mobile Safari's container can
870
+ * be retrieved by running `xcrun simctl get_app_container <sim_udid> com.apple.mobilesafari
871
+ * data`. Use the `xcrun simctl spawn <sim_udid> defaults read <path_to_plist>` command to print
872
+ * the plist content to the Terminal.
873
+ *
874
+ * @group Simulator Only
875
+ * @returns {Promise<void>}
876
+ * @throws {Error} if run on a real device or if the preferences argument is invalid
877
+ * @this {XCUITestDriver}
878
+ */
879
+ async function mobileUpdateSafariPreferences(preferences) {
880
+ if (!this.isSimulator()) {
881
+ throw new Error('This extension is only available for Simulator');
882
+ }
883
+ if (!lodash_1.default.isPlainObject(preferences)) {
884
+ throw new driver_1.errors.InvalidArgumentError('"preferences" argument must be a valid object');
885
+ }
886
+ this.log.debug(`About to update Safari preferences: ${JSON.stringify(preferences)}`);
887
+ await /** @type {import('../driver').Simulator} */ (this.device).updateSafariSettings(preferences);
888
+ }
919
889
  /**
920
890
  * @this {XCUITestDriver}
921
891
  * @param {timing.Timer} timer
@@ -934,6 +904,65 @@ async function generateAtomTimeoutError(timer) {
934
904
  }
935
905
  return new driver_1.errors.TimeoutError(message);
936
906
  }
907
+ /**
908
+ * @this {XCUITestDriver}
909
+ * @param {any} atomsElement
910
+ * @returns {Promise<boolean>}
911
+ */
912
+ async function tapWebElementNatively(atomsElement) {
913
+ // try to get the text of the element, which will be accessible in the
914
+ // native context
915
+ try {
916
+ const [text1, text2] = await bluebird_1.default.all([
917
+ this.executeAtom('get_text', [atomsElement]),
918
+ this.executeAtom('get_attribute_value', [atomsElement, 'value'])
919
+ ]);
920
+ const text = text1 || text2;
921
+ if (!text) {
922
+ return false;
923
+ }
924
+ const els = await this.findNativeElementOrElements('accessibility id', text, true);
925
+ if (![1, 2].includes(els.length)) {
926
+ return false;
927
+ }
928
+ const el = els[0];
929
+ // use tap because on iOS 11.2 and below `nativeClick` crashes WDA
930
+ const rect = /** @type {import('@appium/types').Rect} */ (await this.proxyCommand(`/element/${support_1.util.unwrapElement(el)}/rect`, 'GET'));
931
+ if (els.length > 1) {
932
+ const el2 = els[1];
933
+ const rect2 = /** @type {import('@appium/types').Rect} */ (await this.proxyCommand(`/element/${support_1.util.unwrapElement(el2)}/rect`, 'GET'));
934
+ if (rect.x !== rect2.x || rect.y !== rect2.y
935
+ || rect.width !== rect2.width || rect.height !== rect2.height) {
936
+ // These 2 native elements are not referring to the same web element
937
+ return false;
938
+ }
939
+ }
940
+ await this.mobileTap(rect.x + rect.width / 2, rect.y + rect.height / 2);
941
+ return true;
942
+ }
943
+ catch (err) {
944
+ // any failure should fall through and trigger the more elaborate
945
+ // method of clicking
946
+ this.log.warn(`Error attempting to click: ${err.message}`);
947
+ }
948
+ return false;
949
+ }
950
+ /**
951
+ * @param {any} id
952
+ * @returns {boolean}
953
+ */
954
+ function isValidElementIdentifier(id) {
955
+ if (!lodash_1.default.isString(id) && !lodash_1.default.isNumber(id)) {
956
+ return false;
957
+ }
958
+ if (lodash_1.default.isString(id) && lodash_1.default.isEmpty(id)) {
959
+ return false;
960
+ }
961
+ if (lodash_1.default.isNumber(id) && isNaN(id)) {
962
+ return false;
963
+ }
964
+ return true;
965
+ }
937
966
  /**
938
967
  * @typedef {import('../driver').XCUITestDriver} XCUITestDriver
939
968
  * @typedef {import('@appium/types').Rect} Rect