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
@@ -18,457 +18,461 @@ const DEFAULT_LIST_WEB_FRAMES_RETRIES = 20;
18
18
  const DEFAULT_NATIVE_WINDOW_HANDLE = '1';
19
19
 
20
20
 
21
- const extensions = {
21
+ /**
22
+ * @this {XCUITestDriver}
23
+ * @param {boolean} [useUrl=false]
24
+ * @returns {Promise<import('./types').ViewContext[]>}
25
+ */
26
+ export async function getContextsAndViews(useUrl = true) {
27
+ this.log.debug('Retrieving contexts and views');
28
+ const webviews = await this.listWebFrames(useUrl);
22
29
  /**
23
- * @this {XCUITestDriver}
24
- * @param {boolean} [useUrl=false]
25
- * @returns {Promise<import('./types').ViewContext[]>}
30
+ * @type {import('./types').ViewContext[]}
26
31
  */
27
- async getContextsAndViews(useUrl = true) {
28
- this.log.debug('Retrieving contexts and views');
29
- const webviews = await this.listWebFrames(useUrl);
30
- /**
31
- * @type {import('./types').ViewContext[]}
32
- */
33
- const ctxs = [{id: NATIVE_WIN, view: {}}];
34
- this.contexts = [NATIVE_WIN];
35
- for (const view of webviews) {
36
- ctxs.push({id: `${WEBVIEW_BASE}${view.id}`, view});
37
- this.contexts.push(view.id.toString());
38
- }
39
- return ctxs;
40
- },
32
+ const ctxs = [{id: NATIVE_WIN, view: {}}];
33
+ this.contexts = [NATIVE_WIN];
34
+ for (const view of webviews) {
35
+ ctxs.push({id: `${WEBVIEW_BASE}${view.id}`, view});
36
+ this.contexts.push(view.id.toString());
37
+ }
38
+ return ctxs;
39
+ }
41
40
 
42
- /**
43
- * @deprecated this method is not used anywhere and will be removed in the future
44
- * @this {XCUITestDriver}
45
- * @returns {boolean}
46
- */
47
- useNewSafari() {
48
- return this.isSimulator() && this.isSafari();
49
- },
41
+ /**
42
+ * @deprecated this method is not used anywhere and will be removed in the future
43
+ * @this {XCUITestDriver}
44
+ * @returns {boolean}
45
+ */
46
+ export function useNewSafari() {
47
+ return this.isSimulator() && this.isSafari();
48
+ }
50
49
 
51
- /**
52
- * @this {XCUITestDriver}
53
- * @returns {Promise<void>}
54
- */
55
- async activateRecentWebview() {
56
- this.log.debug('Activating a recent webview');
57
- const timer = new timing.Timer().start();
58
- const contextId = await this.getRecentWebviewContextId(/.*/, /.*/);
59
- if (contextId) {
60
- this.log.info(`Picking webview '${contextId}' after ${timer.getDuration().asMilliSeconds.toFixed(0)}ms`);
61
- await this.setContext(contextId);
62
- return;
63
- }
64
- const appDict = (/** @type {RemoteDebugger} */ (this.remote)).appDict;
65
- const errSuffix = `Make sure your web application is debuggable ` +
66
- `and could be inspected in Safari Web Inspector.`;
67
- if (_.isEmpty(appDict)) {
68
- throw new Error(
69
- `The remote debugger did not return any connected web applications after ` +
70
- `${timer.getDuration().asMilliSeconds.toFixed(0)}ms. ` +
71
- `${errSuffix} ` +
72
- `You may try to change the 'webviewConnectTimeout' capability value to ` +
73
- `customize the retrieval timeout.`
50
+ /**
51
+ * @this {XCUITestDriver}
52
+ * @returns {Promise<void>}
53
+ */
54
+ export async function activateRecentWebview() {
55
+ this.log.debug('Activating a recent webview');
56
+ const timer = new timing.Timer().start();
57
+ const contextId = await this.getRecentWebviewContextId(/.*/, /.*/);
58
+ if (contextId) {
59
+ this.log.info(`Picking webview '${contextId}' after ${timer.getDuration().asMilliSeconds.toFixed(0)}ms`);
60
+ await this.setContext(contextId);
61
+ return;
62
+ }
63
+ const appDict = (/** @type {RemoteDebugger} */ (this.remote)).appDict;
64
+ const errSuffix = `Make sure your web application is debuggable ` +
65
+ `and could be inspected in Safari Web Inspector.`;
66
+ if (_.isEmpty(appDict)) {
67
+ throw new Error(
68
+ `The remote debugger did not return any connected web applications after ` +
69
+ `${timer.getDuration().asMilliSeconds.toFixed(0)}ms. ` +
70
+ `${errSuffix} ` +
71
+ `You may try to change the 'webviewConnectTimeout' capability value to ` +
72
+ `customize the retrieval timeout.`
73
+ );
74
+ }
75
+ const errSuffix2 = `${errSuffix} You may try to change the 'webviewConnectRetries' ` +
76
+ `capability value to customize the amount of pages retrieval retries.`;
77
+ const appsWithPages = _.values(appDict).filter(({pageArray}) => !_.isEmpty(pageArray));
78
+ if (appsWithPages.length > 0) {
79
+ throw new Error(
80
+ `The remote debugger returned ${util.pluralize('web application', appsWithPages.length, true)} ` +
81
+ `with pages after ${timer.getDuration().asMilliSeconds.toFixed(0)}ms, ` +
82
+ `although none of them matched our page search criteria. ${errSuffix2}`
83
+ );
84
+ } else {
85
+ throw new Error(
86
+ `The remote debugger returned ${util.pluralize('web application', _.size(appDict), true)}, ` +
87
+ `but none of them had pages after ${timer.getDuration().asMilliSeconds.toFixed(0)}ms. ` +
88
+ `${errSuffix2} Also, in rare cases the device restart or device OS upgrade may fix this ` +
89
+ `issue if none of the above advices helps.`
90
+ );
91
+ }
92
+ }
93
+
94
+ /**
95
+ * @this {XCUITestDriver}
96
+ * @returns {Promise<import('../types').Page[]>}
97
+ */
98
+ export async function listWebFrames(useUrl = true) {
99
+ const shouldFilterByUrl = useUrl && !this.isRealDevice() && !!this.getCurrentUrl();
100
+ this.log.debug(
101
+ `Selecting by url: ${shouldFilterByUrl}` +
102
+ (shouldFilterByUrl ? ` (expected url: '${this.getCurrentUrl()}')` : '')
103
+ );
104
+
105
+ if (!this.remote) {
106
+ await this.connectToRemoteDebugger();
107
+ }
108
+ const doListPages = async (/** @type {number} */ retries) => {
109
+ try {
110
+ const pageArray = await (/** @type {RemoteDebugger} */ (this.remote)).selectApp(
111
+ shouldFilterByUrl ? this.getCurrentUrl() : undefined,
112
+ retries,
113
+ this.opts.ignoreAboutBlankUrl,
74
114
  );
75
- }
76
- const errSuffix2 = `${errSuffix} You may try to change the 'webviewConnectRetries' ` +
77
- `capability value to customize the amount of pages retrieval retries.`;
78
- const appsWithPages = _.values(appDict).filter(({pageArray}) => !_.isEmpty(pageArray));
79
- if (appsWithPages.length > 0) {
80
- throw new Error(
81
- `The remote debugger returned ${util.pluralize('web application', appsWithPages.length, true)} ` +
82
- `with pages after ${timer.getDuration().asMilliSeconds.toFixed(0)}ms, ` +
83
- `although none of them matched our page search criteria. ${errSuffix2}`
115
+ if (_.isEmpty(pageArray)) {
116
+ // we have no web frames, but continue anyway
117
+ this.log.debug(`No web frames found after ${util.pluralize('retry', retries, true)}`);
118
+ }
119
+ return pageArray;
120
+ } catch (err) {
121
+ this.log.debug(
122
+ `No available web pages after ${util.pluralize('retry', retries, true)}: ${err.message}`
84
123
  );
85
- } else {
86
- throw new Error(
87
- `The remote debugger returned ${util.pluralize('web application', _.size(appDict), true)}, ` +
88
- `but none of them had pages after ${timer.getDuration().asMilliSeconds.toFixed(0)}ms. ` +
89
- `${errSuffix2} Also, in rare cases the device restart or device OS upgrade may fix this ` +
90
- `issue if none of the above advices helps.`
124
+ return [];
125
+ }
126
+ };
127
+
128
+ /** @type {number} */
129
+ const maxRetriesCount = _.isInteger(this.opts.webviewConnectRetries)
130
+ ? Math.max(/** @type {number} */ (this.opts.webviewConnectRetries), 1)
131
+ : DEFAULT_LIST_WEB_FRAMES_RETRIES;
132
+ this.log.debug(
133
+ `About to select a web application with ${util.pluralize('retry', maxRetriesCount, true)} ` +
134
+ `and 500ms interval between each retry. Consider customizing the value of 'webviewConnectRetries' ` +
135
+ `capability to change the amount of retries.`
136
+ );
137
+ return await doListPages(maxRetriesCount);
138
+ }
139
+
140
+ /**
141
+ * @this {XCUITestDriver}
142
+ * @returns {Promise<void>}
143
+ */
144
+ export async function connectToRemoteDebugger() {
145
+ this.remote = await this.getNewRemoteDebugger();
146
+
147
+ // @ts-ignore static is fine
148
+ this.remote.on(RemoteDebugger.EVENT_PAGE_CHANGE, this.onPageChange.bind(this));
149
+ // @ts-ignore static is fine
150
+ this.remote.on(RemoteDebugger.EVENT_FRAMES_DETACHED, () => {
151
+ if (!_.isEmpty(this.curWebFrames)) {
152
+ const curWebFrames = this.curWebFrames;
153
+ this.log.debug(
154
+ `Clearing ${util.pluralize('frame', curWebFrames.length, true)}: ${curWebFrames.join(
155
+ ', ',
156
+ )}`,
91
157
  );
92
158
  }
93
- },
94
- /**
95
- * @this {XCUITestDriver}
96
- * @returns {Promise<import('../types').Page[]>}
97
- */
98
- async listWebFrames(useUrl = true) {
99
- const shouldFilterByUrl = useUrl && !this.isRealDevice() && !!this.getCurrentUrl();
100
- this.log.debug(
101
- `Selecting by url: ${shouldFilterByUrl}` +
102
- (shouldFilterByUrl ? ` (expected url: '${this.getCurrentUrl()}')` : '')
159
+ this.curWebFrames = [];
160
+ });
161
+
162
+ const timeoutMs = this.opts.webviewConnectTimeout ?? DEFAULT_REMOTE_DEBUGGER_CONNECT_TIMEOUT_MS;
163
+ const apps = await this.remote.connect(timeoutMs);
164
+ if (_.isEmpty(apps)) {
165
+ this.log.info(
166
+ `The remote debugger did not report any active web applications within ${timeoutMs}ms timeout. ` +
167
+ `Consider increasing the value of 'webviewConnectTimeout' capability to wait longer ` +
168
+ `on slower devices.`
103
169
  );
170
+ }
171
+ }
104
172
 
105
- if (!this.remote) {
106
- await this.connectToRemoteDebugger();
173
+ /**
174
+ * Retrieves the list of available contexts.
175
+ *
176
+ * The list includes extended context information, like URLs and page names.
177
+ * This is different from the standard `getContexts` API, because the latter
178
+ * only has web view names without any additional information.
179
+ *
180
+ * @remarks In situations where multiple web views are available at once, the
181
+ * client code would have to connect to each of them in order to detect the
182
+ * one which needs to be interacted with. This extra effort is not needed with
183
+ * the information provided by this extension.
184
+ * @param {number} [waitForWebviewMs=0] - The period to poll for available webview(s) (in ms)
185
+ * @returns {Promise<Context[]>} The list of available context objects along with their properties.
186
+ * @this {XCUITestDriver}
187
+ */
188
+ export async function mobileGetContexts(waitForWebviewMs = 0) {
189
+ // make sure it is a number, so the duration check works properly
190
+ if (!_.isNumber(waitForWebviewMs)) {
191
+ waitForWebviewMs = parseInt(waitForWebviewMs, 10);
192
+ if (isNaN(waitForWebviewMs)) {
193
+ waitForWebviewMs = 0;
107
194
  }
108
- const doListPages = async (/** @type {number} */ retries) => {
109
- try {
110
- const pageArray = await (/** @type {RemoteDebugger} */ (this.remote)).selectApp(
111
- shouldFilterByUrl ? this.getCurrentUrl() : undefined,
112
- retries,
113
- this.opts.ignoreAboutBlankUrl,
114
- );
115
- if (_.isEmpty(pageArray)) {
116
- // we have no web frames, but continue anyway
117
- this.log.debug(`No web frames found after ${util.pluralize('retry', retries, true)}`);
118
- }
119
- return pageArray;
120
- } catch (err) {
121
- this.log.debug(
122
- `No available web pages after ${util.pluralize('retry', retries, true)}: ${err.message}`
123
- );
124
- return [];
125
- }
126
- };
195
+ }
127
196
 
128
- /** @type {number} */
129
- const maxRetriesCount = _.isInteger(this.opts.webviewConnectRetries)
130
- ? Math.max(/** @type {number} */ (this.opts.webviewConnectRetries), 1)
131
- : DEFAULT_LIST_WEB_FRAMES_RETRIES;
132
- this.log.debug(
133
- `About to select a web application with ${util.pluralize('retry', maxRetriesCount, true)} ` +
134
- `and 500ms interval between each retry. Consider customizing the value of 'webviewConnectRetries' ` +
135
- `capability to change the amount of retries.`
136
- );
137
- return await doListPages(maxRetriesCount);
138
- },
139
- /**
140
- * @this {XCUITestDriver}
141
- * @returns {Promise<void>}
142
- */
143
- async connectToRemoteDebugger() {
144
- this.remote = await this.getNewRemoteDebugger();
145
-
146
- // @ts-ignore static is fine
147
- this.remote.on(RemoteDebugger.EVENT_PAGE_CHANGE, this.onPageChange.bind(this));
148
- // @ts-ignore static is fine
149
- this.remote.on(RemoteDebugger.EVENT_FRAMES_DETACHED, () => {
150
- if (!_.isEmpty(this.curWebFrames)) {
151
- const curWebFrames = this.curWebFrames;
197
+ const curOpt = this.opts.fullContextList;
198
+ this.opts.fullContextList = true;
199
+
200
+ const timer = new timing.Timer().start();
201
+ try {
202
+ /** @type {FullContext[]} */
203
+ let contexts;
204
+ do {
205
+ contexts = /** @type {FullContext[]} */ (await this.getContexts());
206
+
207
+ if (contexts.length >= 2) {
152
208
  this.log.debug(
153
- `Clearing ${util.pluralize('frame', curWebFrames.length, true)}: ${curWebFrames.join(
154
- ', ',
155
- )}`,
209
+ `Found webview context after ${timer.getDuration().asMilliSeconds.toFixed(0)}ms`,
156
210
  );
211
+ return contexts;
157
212
  }
158
- this.curWebFrames = [];
159
- });
160
-
161
- const timeoutMs = this.opts.webviewConnectTimeout ?? DEFAULT_REMOTE_DEBUGGER_CONNECT_TIMEOUT_MS;
162
- const apps = await this.remote.connect(timeoutMs);
163
- if (_.isEmpty(apps)) {
164
- this.log.info(
165
- `The remote debugger did not report any active web applications within ${timeoutMs}ms timeout. ` +
166
- `Consider increasing the value of 'webviewConnectTimeout' capability to wait longer ` +
167
- `on slower devices.`
168
- );
213
+ this.log.debug(`No webviews found in ${timer.getDuration().asMilliSeconds.toFixed(0)}ms`);
214
+ } while (timer.getDuration().asMilliSeconds < waitForWebviewMs);
215
+ return contexts;
216
+ } finally {
217
+ // reset the option so there are no side effects
218
+ this.opts.fullContextList = curOpt;
219
+ }
220
+ }
221
+
222
+ /**
223
+ * @this {XCUITestDriver}
224
+ * @param {import('./types').PageChangeNotification} pageChangeNotification
225
+ * @returns {Promise<void>}
226
+ */
227
+ export async function onPageChange(pageChangeNotification) {
228
+ this.log.debug(
229
+ `Remote debugger notified us of a new page listing: ${JSON.stringify(
230
+ pageChangeNotification,
231
+ )}`,
232
+ );
233
+ if (this.selectingNewPage) {
234
+ this.log.debug('We are in the middle of selecting a page, ignoring');
235
+ return;
236
+ }
237
+ if (!this.remote?.isConnected) {
238
+ this.log.debug('We have not yet connected, ignoring');
239
+ return;
240
+ }
241
+
242
+ const {appIdKey, pageArray} = pageChangeNotification;
243
+
244
+ /** @type {string[]} */
245
+ const newIds = [];
246
+ /** @type {string[]} */
247
+ const newPages = [];
248
+ /** @type {string|null} */
249
+ let keyId = null;
250
+ for (const page of pageArray) {
251
+ const id = page.id.toString();
252
+ newIds.push(id);
253
+ if (page.isKey) {
254
+ keyId = id;
169
255
  }
170
- },
256
+ const contextId = `${appIdKey}.${id}`;
171
257
 
172
- /**
173
- * Retrieves the list of available contexts.
174
- *
175
- * The list includes extended context information, like URLs and page names.
176
- * This is different from the standard `getContexts` API, because the latter
177
- * only has web view names without any additional information.
178
- *
179
- * @remarks In situations where multiple web views are available at once, the
180
- * client code would have to connect to each of them in order to detect the
181
- * one which needs to be interacted with. This extra effort is not needed with
182
- * the information provided by this extension.
183
- * @param {number} [waitForWebviewMs=0] - The period to poll for available webview(s) (in ms)
184
- * @returns {Promise<Context[]>} The list of available context objects along with their properties.
185
- * @this {XCUITestDriver}
186
- */
187
- async mobileGetContexts(waitForWebviewMs = 0) {
188
- // make sure it is a number, so the duration check works properly
189
- if (!_.isNumber(waitForWebviewMs)) {
190
- waitForWebviewMs = parseInt(waitForWebviewMs, 10);
191
- if (isNaN(waitForWebviewMs)) {
192
- waitForWebviewMs = 0;
258
+ // add if this is a new page
259
+ if (!_.includes(this.contexts, contextId)) {
260
+ if (isUrlIgnored(page.url, this.opts.safariIgnoreWebHostnames)) {
261
+ this.log.info(
262
+ `Not tracking '${page.url}' page because it is blacklisted. ` +
263
+ `'safariIgnoreWebHostnames'=${this.opts.safariIgnoreWebHostnames}`,
264
+ );
265
+ } else {
266
+ newPages.push(id);
267
+ this.contexts.push(contextId);
193
268
  }
194
269
  }
270
+ }
195
271
 
196
- const curOpt = this.opts.fullContextList;
197
- this.opts.fullContextList = true;
272
+ if (!keyId) {
273
+ // if there is no key id, pull the first id from the page array and use that
274
+ // as a stand in
275
+ this.log.debug('No key id found. Choosing first id from page array');
276
+ keyId = newIds[0] || null;
277
+ }
198
278
 
199
- const timer = new timing.Timer().start();
200
- try {
201
- /** @type {FullContext[]} */
202
- let contexts;
203
- do {
204
- contexts = /** @type {FullContext[]} */ (await this.getContexts());
205
-
206
- if (contexts.length >= 2) {
207
- this.log.debug(
208
- `Found webview context after ${timer.getDuration().asMilliSeconds.toFixed(0)}ms`,
209
- );
210
- return contexts;
211
- }
212
- this.log.debug(`No webviews found in ${timer.getDuration().asMilliSeconds.toFixed(0)}ms`);
213
- } while (timer.getDuration().asMilliSeconds < waitForWebviewMs);
214
- return contexts;
215
- } finally {
216
- // reset the option so there are no side effects
217
- this.opts.fullContextList = curOpt;
218
- }
219
- },
220
- /**
221
- * @this {XCUITestDriver}
222
- * @param {import('./types').PageChangeNotification} pageChangeNotification
223
- * @returns {Promise<void>}
224
- */
225
- async onPageChange(pageChangeNotification) {
279
+ if (!util.hasValue(this.curContext)) {
280
+ this.log.debug('We do not appear to have window set yet, ignoring');
281
+ return;
282
+ }
283
+ const [curAppIdKey, curPageIdKey] = this.curContext.split('.');
284
+ if (curAppIdKey !== appIdKey) {
285
+ this.log.debug('Page change not referring to currently selected app, ignoring.');
286
+ return;
287
+ }
288
+
289
+ /** @type {string|null} */
290
+ let newPage = null;
291
+ if (newPages.length) {
292
+ newPage = /** @type {string} */ (_.last(newPages));
293
+ this.log.debug(`We have new pages, selecting page '${newPage}'`);
294
+ } else if (!_.includes(newIds, curPageIdKey)) {
226
295
  this.log.debug(
227
- `Remote debugger notified us of a new page listing: ${JSON.stringify(
228
- pageChangeNotification,
229
- )}`,
296
+ 'New page listing from remote debugger does not contain ' +
297
+ 'current window; assuming it is closed',
230
298
  );
231
- if (this.selectingNewPage) {
232
- this.log.debug('We are in the middle of selecting a page, ignoring');
233
- return;
234
- }
235
- if (!this.remote?.isConnected) {
236
- this.log.debug('We have not yet connected, ignoring');
299
+ if (!util.hasValue(keyId)) {
300
+ this.log.error(
301
+ 'Do not have our current window anymore, and there ' +
302
+ 'are not any more to load! Doing nothing...',
303
+ );
304
+ this.setCurrentUrl(undefined);
237
305
  return;
238
306
  }
239
307
 
240
- const {appIdKey, pageArray} = pageChangeNotification;
241
-
242
- /** @type {string[]} */
243
- const newIds = [];
244
- /** @type {string[]} */
245
- const newPages = [];
246
- /** @type {string|null} */
247
- let keyId = null;
248
- for (const page of pageArray) {
249
- const id = page.id.toString();
250
- newIds.push(id);
251
- if (page.isKey) {
252
- keyId = id;
253
- }
254
- const contextId = `${appIdKey}.${id}`;
255
-
256
- // add if this is a new page
257
- if (!_.includes(this.contexts, contextId)) {
258
- if (isUrlIgnored(page.url, this.opts.safariIgnoreWebHostnames)) {
259
- this.log.info(
260
- `Not tracking '${page.url}' page because it is blacklisted. ` +
261
- `'safariIgnoreWebHostnames'=${this.opts.safariIgnoreWebHostnames}`,
262
- );
263
- } else {
264
- newPages.push(id);
265
- this.contexts.push(contextId);
266
- }
267
- }
268
- }
308
+ this.log.debug(`Debugger already selected page '${keyId}', ` + `confirming that choice.`);
309
+ this.curContext = `${appIdKey}.${keyId}`;
310
+ newPage = keyId;
311
+ } else {
312
+ // at this point, there are no new pages, and the current page still exists
313
+ this.log.debug('New page listing is same as old, doing nothing');
314
+ }
269
315
 
270
- if (!keyId) {
271
- // if there is no key id, pull the first id from the page array and use that
272
- // as a stand in
273
- this.log.debug('No key id found. Choosing first id from page array');
274
- keyId = newIds[0] || null;
316
+ // make sure that the page listing isn't indicating a redirect
317
+ if (util.hasValue(this.curContext)) {
318
+ const currentPageId = parseInt(String(_.last(this.curContext.split('.'))), 10);
319
+ const page = _.find(pageArray, (p) => parseInt(String(p.id), 10) === currentPageId);
320
+ if (page && page.url !== this.getCurrentUrl()) {
321
+ this.log.debug(`Redirected from '${this.getCurrentUrl()}' to '${page.url}'`);
322
+ this.setCurrentUrl(page.url);
275
323
  }
324
+ }
276
325
 
277
- if (!util.hasValue(this.curContext)) {
278
- this.log.debug('We do not appear to have window set yet, ignoring');
279
- return;
280
- }
281
- const [curAppIdKey, curPageIdKey] = this.curContext.split('.');
282
- if (curAppIdKey !== appIdKey) {
283
- this.log.debug('Page change not referring to currently selected app, ignoring.');
284
- return;
326
+ if (util.hasValue(newPage)) {
327
+ this.selectingNewPage = true;
328
+ const oldContext = this.curContext;
329
+ this.curContext = `${appIdKey}.${newPage}`;
330
+ try {
331
+ await this.remote.selectPage(appIdKey, parseInt(newPage, 10));
332
+ await notifyBiDiContextChange.bind(this)();
333
+ } catch (e) {
334
+ this.log.warn(`Failed to select page: ${e.message}`);
335
+ this.curContext = oldContext;
336
+ } finally {
337
+ this.selectingNewPage = false;
285
338
  }
339
+ }
340
+ }
286
341
 
287
- /** @type {string|null} */
288
- let newPage = null;
289
- if (newPages.length) {
290
- newPage = /** @type {string} */ (_.last(newPages));
291
- this.log.debug(`We have new pages, selecting page '${newPage}'`);
292
- } else if (!_.includes(newIds, curPageIdKey)) {
293
- this.log.debug(
294
- 'New page listing from remote debugger does not contain ' +
295
- 'current window; assuming it is closed',
296
- );
297
- if (!util.hasValue(keyId)) {
298
- this.log.error(
299
- 'Do not have our current window anymore, and there ' +
300
- 'are not any more to load! Doing nothing...',
301
- );
302
- this.setCurrentUrl(undefined);
303
- return;
304
- }
342
+ /**
343
+ * @this {XCUITestDriver}
344
+ * @returns {Promise<void>}
345
+ */
346
+ export async function stopRemote(closeWindowBeforeDisconnecting = false) {
347
+ if (!this.remote) {
348
+ throw this.log.errorWithException('Tried to leave a web frame but were not in one');
349
+ }
305
350
 
306
- this.log.debug(`Debugger already selected page '${keyId}', ` + `confirming that choice.`);
307
- this.curContext = `${appIdKey}.${keyId}`;
308
- newPage = keyId;
309
- } else {
310
- // at this point, there are no new pages, and the current page still exists
311
- this.log.debug('New page listing is same as old, doing nothing');
312
- }
351
+ if (closeWindowBeforeDisconnecting) {
352
+ await this.closeWindow();
353
+ }
354
+ await this.remote.disconnect();
355
+ this.curContext = null;
356
+ await notifyBiDiContextChange.bind(this)();
357
+ this.curWebFrames = [];
358
+ this.remote = null;
359
+ }
313
360
 
314
- // make sure that the page listing isn't indicating a redirect
315
- if (util.hasValue(this.curContext)) {
316
- const currentPageId = parseInt(String(_.last(this.curContext.split('.'))), 10);
317
- const page = _.find(pageArray, (p) => parseInt(String(p.id), 10) === currentPageId);
318
- if (page && page.url !== this.getCurrentUrl()) {
319
- this.log.debug(`Redirected from '${this.getCurrentUrl()}' to '${page.url}'`);
320
- this.setCurrentUrl(page.url);
321
- }
322
- }
361
+ /**
362
+ * @this {XCUITestDriver}
363
+ * @param {string|undefined|null} url
364
+ */
365
+ export function setCurrentUrl(url) {
366
+ this._currentUrl = url;
367
+ }
323
368
 
324
- if (util.hasValue(newPage)) {
325
- this.selectingNewPage = true;
326
- const oldContext = this.curContext;
327
- this.curContext = `${appIdKey}.${newPage}`;
328
- try {
329
- await this.remote.selectPage(appIdKey, parseInt(newPage, 10));
330
- await notifyBiDiContextChange.bind(this)();
331
- } catch (e) {
332
- this.log.warn(`Failed to select page: ${e.message}`);
333
- this.curContext = oldContext;
334
- } finally {
335
- this.selectingNewPage = false;
336
- }
337
- }
338
- },
339
- };
369
+ /**
370
+ * @this {XCUITestDriver}
371
+ * @returns {string|undefined|null}
372
+ */
373
+ export function getCurrentUrl() {
374
+ return this._currentUrl;
375
+ }
340
376
 
341
- const helpers = {
342
- /**
343
- * @this {XCUITestDriver}
344
- * @returns {Promise<void>}
345
- */
346
- async stopRemote(closeWindowBeforeDisconnecting = false) {
347
- if (!this.remote) {
348
- throw this.log.errorWithException('Tried to leave a web frame but were not in one');
349
- }
377
+ /**
378
+ * @param {RegExp} titleRegExp
379
+ * @param {RegExp} urlRegExp
380
+ * @this {XCUITestDriver}
381
+ * @returns {Promise<string|undefined>}
382
+ */
383
+ export async function getRecentWebviewContextId(titleRegExp, urlRegExp) {
384
+ if (!_.isRegExp(titleRegExp) && !_.isRegExp(urlRegExp)) {
385
+ throw new errors.InvalidArgumentError(
386
+ 'A regular expression for either web view title or url must be provided',
387
+ );
388
+ }
350
389
 
351
- if (closeWindowBeforeDisconnecting) {
352
- await this.closeWindow();
353
- }
354
- await this.remote.disconnect();
355
- this.curContext = null;
356
- await notifyBiDiContextChange.bind(this)();
357
- this.curWebFrames = [];
358
- this.remote = null;
359
- },
360
- /**
361
- * @this {XCUITestDriver}
362
- * @param {string|undefined|null} url
363
- */
364
- setCurrentUrl(url) {
365
- this._currentUrl = url;
366
- },
367
- /**
368
- * @this {XCUITestDriver}
369
- * @returns {string|undefined|null}
370
- */
371
- getCurrentUrl() {
372
- return this._currentUrl;
373
- },
374
- /**
375
- * @param {RegExp} titleRegExp
376
- * @param {RegExp} urlRegExp
377
- * @this {XCUITestDriver}
378
- * @returns {Promise<string|undefined>}
379
- */
380
- async getRecentWebviewContextId(titleRegExp, urlRegExp) {
381
- if (!_.isRegExp(titleRegExp) && !_.isRegExp(urlRegExp)) {
382
- throw new errors.InvalidArgumentError(
383
- 'A regular expression for either web view title or url must be provided',
384
- );
390
+ const currentUrl = this.getCurrentUrl();
391
+ const contexts = _.filter(await this.getContextsAndViews(false), 'view');
392
+ // first try to match by current url
393
+ if (currentUrl) {
394
+ const ctx = contexts.find(({view}) => (view?.url || '') === currentUrl);
395
+ if (ctx) {
396
+ return ctx.id;
385
397
  }
398
+ }
399
+ // if not, try to match by regular expression
400
+ return contexts.find(
401
+ ({view}) =>
402
+ (view?.title && titleRegExp?.test(view.title)) || (view?.url && urlRegExp?.test(view.url)),
403
+ )?.id;
404
+ }
386
405
 
387
- const currentUrl = this.getCurrentUrl();
388
- const contexts = _.filter(await this.getContextsAndViews(false), 'view');
389
- // first try to match by current url
390
- if (currentUrl) {
391
- const ctx = contexts.find(({view}) => (view?.url || '') === currentUrl);
392
- if (ctx) {
393
- return ctx.id;
394
- }
395
- }
396
- // if not, try to match by regular expression
397
- return contexts.find(
398
- ({view}) =>
399
- (view?.title && titleRegExp?.test(view.title)) || (view?.url && urlRegExp?.test(view.url)),
400
- )?.id;
401
- },
402
- /**
403
- * @this {XCUITestDriver}
404
- * @returns {boolean}
405
- */
406
- isWebContext() {
407
- return !!this.curContext && this.curContext !== NATIVE_WIN;
408
- },
409
- /**
410
- * @this {XCUITestDriver}
411
- * @returns {boolean}
412
- */
413
- isWebview() {
414
- return this.isWebContext();
415
- },
416
- /**
417
- * @this {XCUITestDriver}
418
- * @returns {Promise<RemoteDebugger>}
419
- */
420
- async getNewRemoteDebugger() {
421
- const socketPath = this.isRealDevice()
422
- ? undefined
423
- : (await /** @type {import('../driver').Simulator} */ (this.device).getWebInspectorSocket() ?? undefined);
424
- return createRemoteDebugger(
425
- {
426
- bundleId: this.opts.bundleId,
427
- additionalBundleIds: this.opts.additionalWebviewBundleIds,
428
- isSafari: this.isSafari(),
429
- includeSafari: this.opts.includeSafariInWebviews,
430
- pageLoadMs: this.pageLoadMs,
431
- platformVersion: this.opts.platformVersion,
432
- socketPath,
433
- remoteDebugProxy: this.opts.remoteDebugProxy,
434
- garbageCollectOnExecute: util.hasValue(this.opts.safariGarbageCollect)
435
- ? !!this.opts.safariGarbageCollect
436
- : false,
437
- udid: this.opts.udid,
438
- logAllCommunication: this.opts.safariLogAllCommunication,
439
- logAllCommunicationHexDump: this.opts.safariLogAllCommunicationHexDump,
440
- socketChunkSize: this.opts.safariSocketChunkSize,
441
- webInspectorMaxFrameLength: this.opts.safariWebInspectorMaxFrameLength,
442
- pageLoadStrategy: this.caps.pageLoadStrategy,
443
- },
444
- this.isRealDevice(),
445
- );
446
- },
447
- };
406
+ /**
407
+ * @this {XCUITestDriver}
408
+ * @returns {boolean}
409
+ */
410
+ export function isWebContext() {
411
+ return !!this.curContext && this.curContext !== NATIVE_WIN;
412
+ }
448
413
 
449
- const commands = {
450
- /**
451
- * @this {XCUITestDriver}
452
- * @returns {Promise<string>}
453
- */
454
- async getCurrentContext() {
455
- if (this.curContext && this.curContext !== NATIVE_WIN) {
456
- return `${WEBVIEW_BASE}${this.curContext}`;
457
- }
458
- return NATIVE_WIN;
459
- },
414
+ /**
415
+ * @this {XCUITestDriver}
416
+ * @returns {boolean}
417
+ */
418
+ export function isWebview() {
419
+ return this.isWebContext();
420
+ }
460
421
 
461
- /**
462
- * Set context
463
- *
464
- * @param {string|Context} name - The name of context to set. It could be 'null' as NATIVE_WIN.
465
- * @param {any} [callback] The callback. (It is not called in this method)
466
- * @param {boolean} [skipReadyCheck=false] - Whether it waits for the new context is ready
467
- * @this {XCUITestDriver}
468
- * @returns {Promise<void>}
469
- */
470
- async setContext(name, callback, skipReadyCheck = false) {
471
- function alreadyInContext(desired, current) {
422
+ /**
423
+ * @this {XCUITestDriver}
424
+ * @returns {Promise<RemoteDebugger>}
425
+ */
426
+ export async function getNewRemoteDebugger() {
427
+ const socketPath = this.isRealDevice()
428
+ ? undefined
429
+ : (await /** @type {import('../driver').Simulator} */ (this.device).getWebInspectorSocket() ?? undefined);
430
+ return createRemoteDebugger(
431
+ {
432
+ bundleId: this.opts.bundleId,
433
+ additionalBundleIds: this.opts.additionalWebviewBundleIds,
434
+ isSafari: this.isSafari(),
435
+ includeSafari: this.opts.includeSafariInWebviews,
436
+ pageLoadMs: this.pageLoadMs,
437
+ platformVersion: this.opts.platformVersion,
438
+ socketPath,
439
+ remoteDebugProxy: this.opts.remoteDebugProxy,
440
+ garbageCollectOnExecute: util.hasValue(this.opts.safariGarbageCollect)
441
+ ? !!this.opts.safariGarbageCollect
442
+ : false,
443
+ udid: this.opts.udid,
444
+ logAllCommunication: this.opts.safariLogAllCommunication,
445
+ logAllCommunicationHexDump: this.opts.safariLogAllCommunicationHexDump,
446
+ socketChunkSize: this.opts.safariSocketChunkSize,
447
+ webInspectorMaxFrameLength: this.opts.safariWebInspectorMaxFrameLength,
448
+ pageLoadStrategy: this.caps.pageLoadStrategy,
449
+ },
450
+ this.isRealDevice(),
451
+ );
452
+ }
453
+
454
+ /**
455
+ * @this {XCUITestDriver}
456
+ * @returns {Promise<string>}
457
+ */
458
+ export async function getCurrentContext() {
459
+ if (this.curContext && this.curContext !== NATIVE_WIN) {
460
+ return `${WEBVIEW_BASE}${this.curContext}`;
461
+ }
462
+ return NATIVE_WIN;
463
+ }
464
+
465
+ /**
466
+ * Set context
467
+ *
468
+ * @param {string|Context} name - The name of context to set. It could be 'null' as NATIVE_WIN.
469
+ * @param {any} [callback] The callback. (It is not called in this method)
470
+ * @param {boolean} [skipReadyCheck=false] - Whether it waits for the new context is ready
471
+ * @this {XCUITestDriver}
472
+ * @returns {Promise<void>}
473
+ */
474
+ export async function setContext(name, callback, skipReadyCheck = false) {
475
+ function alreadyInContext(desired, current) {
472
476
  return (
473
477
  desired === current ||
474
478
  (desired === null && current === NATIVE_WIN) ||
@@ -563,88 +567,90 @@ const commands = {
563
567
  if (this.logs.safariNetwork) {
564
568
  (/** @type {RemoteDebugger} */ (this.remote)).startNetwork(
565
569
  this.logs.safariNetwork.onNetworkEvent.bind(this.logs.safariNetwork),
566
- );
567
- }
568
- }
569
- },
570
- /**
571
- * @this {XCUITestDriver}
572
- * @returns {Promise<string[]|FullContext[]>}
573
- */
574
- async getContexts() {
575
- this.log.debug('Getting list of available contexts');
576
- const contexts = await this.getContextsAndViews(false);
577
-
578
- if (this.opts.fullContextList) {
579
- return /** @type {import('./types').FullContext[]} */ (
580
- contexts.map((context) => ({
581
- id: context.id.toString(),
582
- title: context.view?.title,
583
- url: context.view?.url,
584
- bundleId: context.view?.bundleId,
585
- }))
586
570
  );
587
571
  }
588
- return /** @type {string[]} */ (contexts.map((context) => context.id.toString()));
589
- },
572
+ }
573
+ }
590
574
 
591
- /**
592
- * @this {XCUITestDriver}
593
- * @param {string} name
594
- * @param {boolean} [skipReadyCheck]
595
- * @returns {Promise<void>}
596
- */
597
- async setWindow(name, skipReadyCheck) {
598
- if (!this.isWebContext()) {
599
- // https://github.com/appium/appium/issues/20710
600
- return;
601
- }
602
- try {
603
- await this.setContext(name, _.noop, skipReadyCheck);
604
- } catch (err) {
605
- // translate the error in terms of windows
606
- throw isErrorType(err, errors.NoSuchContextError) ? new errors.NoSuchWindowError() : err;
607
- }
608
- },
609
- /**
610
- * @this {XCUITestDriver}
611
- * @returns {Promise<string>}
612
- */
613
- async getWindowHandle() {
614
- if (!this.isWebContext()) {
615
- // https://github.com/appium/appium/issues/20710
616
- return DEFAULT_NATIVE_WINDOW_HANDLE;
617
- }
618
- if (!this.curContext) {
619
- throw new errors.InvalidContextError();
620
- }
621
- this.log.debug(`Getting current window handle`);
622
- return this.curContext;
623
- },
624
- /**
625
- * @this {XCUITestDriver}
626
- * @returns {Promise<string[]>}
627
- */
628
- async getWindowHandles() {
629
- if (!this.isWebContext()) {
630
- // https://github.com/appium/appium/issues/20710
631
- return [DEFAULT_NATIVE_WINDOW_HANDLE];
632
- }
633
- this.log.debug('Getting list of available window handles');
634
- const contexts = await this.getContextsAndViews(false);
635
- return (
636
- contexts
637
- // get rid of the native app context
638
- .filter((context) => context.id !== NATIVE_WIN)
639
- // get the `app.id` format expected
640
- .map((context) =>
641
- // This is non-nullable because the `FullContext` having `id` `NATIVE_WIN`
642
- // _looks like_ the only with an empty view.
643
- context.view?.id?.toString() ?? ''
644
- )
575
+ /**
576
+ * @this {XCUITestDriver}
577
+ * @returns {Promise<string[]|FullContext[]>}
578
+ */
579
+ export async function getContexts() {
580
+ this.log.debug('Getting list of available contexts');
581
+ const contexts = await this.getContextsAndViews(false);
582
+
583
+ if (this.opts.fullContextList) {
584
+ return /** @type {import('./types').FullContext[]} */ (
585
+ contexts.map((context) => ({
586
+ id: context.id.toString(),
587
+ title: context.view?.title,
588
+ url: context.view?.url,
589
+ bundleId: context.view?.bundleId,
590
+ }))
645
591
  );
646
- },
647
- };
592
+ }
593
+ return /** @type {string[]} */ (contexts.map((context) => context.id.toString()));
594
+ }
595
+
596
+ /**
597
+ * @this {XCUITestDriver}
598
+ * @param {string} name
599
+ * @param {boolean} [skipReadyCheck]
600
+ * @returns {Promise<void>}
601
+ */
602
+ export async function setWindow(name, skipReadyCheck) {
603
+ if (!this.isWebContext()) {
604
+ // https://github.com/appium/appium/issues/20710
605
+ return;
606
+ }
607
+ try {
608
+ await this.setContext(name, _.noop, skipReadyCheck);
609
+ } catch (err) {
610
+ // translate the error in terms of windows
611
+ throw isErrorType(err, errors.NoSuchContextError) ? new errors.NoSuchWindowError() : err;
612
+ }
613
+ }
614
+
615
+ /**
616
+ * @this {XCUITestDriver}
617
+ * @returns {Promise<string>}
618
+ */
619
+ export async function getWindowHandle() {
620
+ if (!this.isWebContext()) {
621
+ // https://github.com/appium/appium/issues/20710
622
+ return DEFAULT_NATIVE_WINDOW_HANDLE;
623
+ }
624
+ if (!this.curContext) {
625
+ throw new errors.InvalidContextError();
626
+ }
627
+ this.log.debug(`Getting current window handle`);
628
+ return this.curContext;
629
+ }
630
+
631
+ /**
632
+ * @this {XCUITestDriver}
633
+ * @returns {Promise<string[]>}
634
+ */
635
+ export async function getWindowHandles() {
636
+ if (!this.isWebContext()) {
637
+ // https://github.com/appium/appium/issues/20710
638
+ return [DEFAULT_NATIVE_WINDOW_HANDLE];
639
+ }
640
+ this.log.debug('Getting list of available window handles');
641
+ const contexts = await this.getContextsAndViews(false);
642
+ return (
643
+ contexts
644
+ // get rid of the native app context
645
+ .filter((context) => context.id !== NATIVE_WIN)
646
+ // get the `app.id` format expected
647
+ .map((context) =>
648
+ // This is non-nullable because the `FullContext` having `id` `NATIVE_WIN`
649
+ // _looks like_ the only with an empty view.
650
+ context.view?.id?.toString() ?? ''
651
+ )
652
+ );
653
+ }
648
654
 
649
655
  /**
650
656
  * Checks if a URL is blacklisted in the 'safariIgnoreWebHostnames' capability
@@ -691,7 +697,6 @@ export async function notifyBiDiContextChange() {
691
697
  this.eventEmitter.emit(BIDI_EVENT_NAME, makeObsoleteContextUpdatedEvent(name));
692
698
  }
693
699
 
694
- export default {...helpers, ...extensions, ...commands};
695
700
 
696
701
  /**
697
702
  * @typedef {import('../driver').XCUITestDriver} XCUITestDriver