appium-xcuitest-driver 10.3.0 → 10.4.1

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 (257) 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 -38
  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 +275 -197
  175. package/build/lib/commands/web.d.ts.map +1 -1
  176. package/build/lib/commands/web.js +866 -785
  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 +277 -1597
  187. package/build/lib/driver.d.ts.map +1 -1
  188. package/build/lib/driver.js +318 -235
  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/lib/commands/active-app-info.js +12 -0
  194. package/lib/commands/alert.js +68 -65
  195. package/lib/commands/app-management.js +308 -301
  196. package/lib/commands/app-strings.js +24 -26
  197. package/lib/commands/appearance.js +54 -56
  198. package/lib/commands/audit.js +18 -20
  199. package/lib/commands/battery.js +35 -37
  200. package/lib/commands/biometric.js +44 -46
  201. package/lib/commands/certificate.js +226 -215
  202. package/lib/commands/clipboard.js +30 -32
  203. package/lib/commands/condition.js +98 -100
  204. package/lib/commands/content-size.js +36 -38
  205. package/lib/commands/context.js +495 -490
  206. package/lib/commands/deviceInfo.js +19 -20
  207. package/lib/commands/element.js +367 -357
  208. package/lib/commands/execute.js +72 -72
  209. package/lib/commands/file-movement.js +132 -134
  210. package/lib/commands/find.js +160 -159
  211. package/lib/commands/general.js +238 -231
  212. package/lib/commands/geolocation.js +6 -14
  213. package/lib/commands/gesture.js +525 -515
  214. package/lib/commands/increase-contrast.js +30 -32
  215. package/lib/commands/iohid.js +32 -34
  216. package/lib/commands/keyboard.js +49 -51
  217. package/lib/commands/keychains.js +12 -14
  218. package/lib/commands/localization.js +24 -26
  219. package/lib/commands/location.js +102 -104
  220. package/lib/commands/lock.js +38 -38
  221. package/lib/commands/log.js +197 -198
  222. package/lib/commands/memory.js +40 -42
  223. package/lib/commands/navigation.js +96 -100
  224. package/lib/commands/notifications.js +57 -59
  225. package/lib/commands/pasteboard.js +37 -39
  226. package/lib/commands/pcap.js +84 -86
  227. package/lib/commands/performance.js +132 -133
  228. package/lib/commands/permissions.js +67 -69
  229. package/lib/commands/proxy-helper.js +60 -61
  230. package/lib/commands/record-audio.js +115 -120
  231. package/lib/commands/recordscreen.js +145 -149
  232. package/lib/commands/screenshots.js +116 -116
  233. package/lib/commands/simctl.js +25 -29
  234. package/lib/commands/source.js +42 -46
  235. package/lib/commands/timeouts.js +59 -63
  236. package/lib/commands/web.js +932 -859
  237. package/lib/commands/xctest-record-screen.js +103 -105
  238. package/lib/commands/xctest.js +134 -139
  239. package/lib/driver.js +286 -235
  240. package/lib/execute-method-map.ts +9 -0
  241. package/npm-shrinkwrap.json +8 -8
  242. package/package.json +1 -1
  243. package/build/lib/commands/activeAppInfo.d.ts +0 -12
  244. package/build/lib/commands/activeAppInfo.d.ts.map +0 -1
  245. package/build/lib/commands/activeAppInfo.js +0 -15
  246. package/build/lib/commands/activeAppInfo.js.map +0 -1
  247. package/build/lib/commands/index.d.ts +0 -96
  248. package/build/lib/commands/index.d.ts.map +0 -1
  249. package/build/lib/commands/index.js +0 -100
  250. package/build/lib/commands/index.js.map +0 -1
  251. package/build/lib/cookies.d.ts +0 -15
  252. package/build/lib/cookies.d.ts.map +0 -1
  253. package/build/lib/cookies.js +0 -84
  254. package/build/lib/cookies.js.map +0 -1
  255. package/lib/commands/activeAppInfo.js +0 -14
  256. package/lib/commands/index.js +0 -95
  257. package/lib/cookies.js +0 -92
@@ -3,6 +3,26 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.getContextsAndViews = getContextsAndViews;
7
+ exports.useNewSafari = useNewSafari;
8
+ exports.activateRecentWebview = activateRecentWebview;
9
+ exports.listWebFrames = listWebFrames;
10
+ exports.connectToRemoteDebugger = connectToRemoteDebugger;
11
+ exports.mobileGetContexts = mobileGetContexts;
12
+ exports.onPageChange = onPageChange;
13
+ exports.stopRemote = stopRemote;
14
+ exports.setCurrentUrl = setCurrentUrl;
15
+ exports.getCurrentUrl = getCurrentUrl;
16
+ exports.getRecentWebviewContextId = getRecentWebviewContextId;
17
+ exports.isWebContext = isWebContext;
18
+ exports.isWebview = isWebview;
19
+ exports.getNewRemoteDebugger = getNewRemoteDebugger;
20
+ exports.getCurrentContext = getCurrentContext;
21
+ exports.setContext = setContext;
22
+ exports.getContexts = getContexts;
23
+ exports.setWindow = setWindow;
24
+ exports.getWindowHandle = getWindowHandle;
25
+ exports.getWindowHandles = getWindowHandles;
6
26
  exports.notifyBiDiContextChange = notifyBiDiContextChange;
7
27
  const appium_remote_debugger_1 = require("appium-remote-debugger");
8
28
  const driver_1 = require("appium/driver");
@@ -18,547 +38,541 @@ const WEBVIEW_BASE = `${WEBVIEW_WIN}_`;
18
38
  const DEFAULT_REMOTE_DEBUGGER_CONNECT_TIMEOUT_MS = 5000;
19
39
  const DEFAULT_LIST_WEB_FRAMES_RETRIES = 20;
20
40
  const DEFAULT_NATIVE_WINDOW_HANDLE = '1';
21
- const extensions = {
22
- /**
23
- * @this {XCUITestDriver}
24
- * @param {boolean} [useUrl=false]
25
- * @returns {Promise<import('./types').ViewContext[]>}
26
- */
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: utils_1.NATIVE_WIN, view: {} }];
34
- this.contexts = [utils_1.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
- },
41
- /**
42
- * @deprecated this method is not used anywhere and will be removed in the future
43
- * @this {XCUITestDriver}
44
- * @returns {boolean}
45
- */
46
- useNewSafari() {
47
- return this.isSimulator() && this.isSafari();
48
- },
49
- /**
50
- * @this {XCUITestDriver}
51
- * @returns {Promise<void>}
52
- */
53
- async activateRecentWebview() {
54
- this.log.debug('Activating a recent webview');
55
- const timer = new support_1.timing.Timer().start();
56
- const contextId = await this.getRecentWebviewContextId(/.*/, /.*/);
57
- if (contextId) {
58
- this.log.info(`Picking webview '${contextId}' after ${timer.getDuration().asMilliSeconds.toFixed(0)}ms`);
59
- await this.setContext(contextId);
60
- return;
61
- }
62
- const appDict = ( /** @type {RemoteDebugger} */(this.remote)).appDict;
63
- const errSuffix = `Make sure your web application is debuggable ` +
64
- `and could be inspected in Safari Web Inspector.`;
65
- if (lodash_1.default.isEmpty(appDict)) {
66
- throw new Error(`The remote debugger did not return any connected web applications after ` +
67
- `${timer.getDuration().asMilliSeconds.toFixed(0)}ms. ` +
68
- `${errSuffix} ` +
69
- `You may try to change the 'webviewConnectTimeout' capability value to ` +
70
- `customize the retrieval timeout.`);
71
- }
72
- const errSuffix2 = `${errSuffix} You may try to change the 'webviewConnectRetries' ` +
73
- `capability value to customize the amount of pages retrieval retries.`;
74
- const appsWithPages = lodash_1.default.values(appDict).filter(({ pageArray }) => !lodash_1.default.isEmpty(pageArray));
75
- if (appsWithPages.length > 0) {
76
- throw new Error(`The remote debugger returned ${support_1.util.pluralize('web application', appsWithPages.length, true)} ` +
77
- `with pages after ${timer.getDuration().asMilliSeconds.toFixed(0)}ms, ` +
78
- `although none of them matched our page search criteria. ${errSuffix2}`);
79
- }
80
- else {
81
- throw new Error(`The remote debugger returned ${support_1.util.pluralize('web application', lodash_1.default.size(appDict), true)}, ` +
82
- `but none of them had pages after ${timer.getDuration().asMilliSeconds.toFixed(0)}ms. ` +
83
- `${errSuffix2} Also, in rare cases the device restart or device OS upgrade may fix this ` +
84
- `issue if none of the above advices helps.`);
85
- }
86
- },
87
- /**
88
- * @this {XCUITestDriver}
89
- * @returns {Promise<import('../types').Page[]>}
90
- */
91
- async listWebFrames(useUrl = true) {
92
- const shouldFilterByUrl = useUrl && !this.isRealDevice() && !!this.getCurrentUrl();
93
- this.log.debug(`Selecting by url: ${shouldFilterByUrl}` +
94
- (shouldFilterByUrl ? ` (expected url: '${this.getCurrentUrl()}')` : ''));
95
- if (!this.remote) {
96
- await this.connectToRemoteDebugger();
97
- }
98
- const doListPages = async (/** @type {number} */ retries) => {
99
- try {
100
- const pageArray = await ( /** @type {RemoteDebugger} */(this.remote)).selectApp(shouldFilterByUrl ? this.getCurrentUrl() : undefined, retries, this.opts.ignoreAboutBlankUrl);
101
- if (lodash_1.default.isEmpty(pageArray)) {
102
- // we have no web frames, but continue anyway
103
- this.log.debug(`No web frames found after ${support_1.util.pluralize('retry', retries, true)}`);
104
- }
105
- return pageArray;
106
- }
107
- catch (err) {
108
- this.log.debug(`No available web pages after ${support_1.util.pluralize('retry', retries, true)}: ${err.message}`);
109
- return [];
110
- }
111
- };
112
- /** @type {number} */
113
- const maxRetriesCount = lodash_1.default.isInteger(this.opts.webviewConnectRetries)
114
- ? Math.max(/** @type {number} */ (this.opts.webviewConnectRetries), 1)
115
- : DEFAULT_LIST_WEB_FRAMES_RETRIES;
116
- this.log.debug(`About to select a web application with ${support_1.util.pluralize('retry', maxRetriesCount, true)} ` +
117
- `and 500ms interval between each retry. Consider customizing the value of 'webviewConnectRetries' ` +
118
- `capability to change the amount of retries.`);
119
- return await doListPages(maxRetriesCount);
120
- },
121
- /**
122
- * @this {XCUITestDriver}
123
- * @returns {Promise<void>}
124
- */
125
- async connectToRemoteDebugger() {
126
- this.remote = await this.getNewRemoteDebugger();
127
- // @ts-ignore static is fine
128
- this.remote.on(appium_remote_debugger_1.RemoteDebugger.EVENT_PAGE_CHANGE, this.onPageChange.bind(this));
129
- // @ts-ignore static is fine
130
- this.remote.on(appium_remote_debugger_1.RemoteDebugger.EVENT_FRAMES_DETACHED, () => {
131
- if (!lodash_1.default.isEmpty(this.curWebFrames)) {
132
- const curWebFrames = this.curWebFrames;
133
- this.log.debug(`Clearing ${support_1.util.pluralize('frame', curWebFrames.length, true)}: ${curWebFrames.join(', ')}`);
134
- }
135
- this.curWebFrames = [];
136
- });
137
- const timeoutMs = this.opts.webviewConnectTimeout ?? DEFAULT_REMOTE_DEBUGGER_CONNECT_TIMEOUT_MS;
138
- const apps = await this.remote.connect(timeoutMs);
139
- if (lodash_1.default.isEmpty(apps)) {
140
- this.log.info(`The remote debugger did not report any active web applications within ${timeoutMs}ms timeout. ` +
141
- `Consider increasing the value of 'webviewConnectTimeout' capability to wait longer ` +
142
- `on slower devices.`);
143
- }
144
- },
41
+ /**
42
+ * @this {XCUITestDriver}
43
+ * @param {boolean} [useUrl=false]
44
+ * @returns {Promise<import('./types').ViewContext[]>}
45
+ */
46
+ async function getContextsAndViews(useUrl = true) {
47
+ this.log.debug('Retrieving contexts and views');
48
+ const webviews = await this.listWebFrames(useUrl);
145
49
  /**
146
- * Retrieves the list of available contexts.
147
- *
148
- * The list includes extended context information, like URLs and page names.
149
- * This is different from the standard `getContexts` API, because the latter
150
- * only has web view names without any additional information.
151
- *
152
- * @remarks In situations where multiple web views are available at once, the
153
- * client code would have to connect to each of them in order to detect the
154
- * one which needs to be interacted with. This extra effort is not needed with
155
- * the information provided by this extension.
156
- * @param {number} [waitForWebviewMs=0] - The period to poll for available webview(s) (in ms)
157
- * @returns {Promise<Context[]>} The list of available context objects along with their properties.
158
- * @this {XCUITestDriver}
50
+ * @type {import('./types').ViewContext[]}
159
51
  */
160
- async mobileGetContexts(waitForWebviewMs = 0) {
161
- // make sure it is a number, so the duration check works properly
162
- if (!lodash_1.default.isNumber(waitForWebviewMs)) {
163
- waitForWebviewMs = parseInt(waitForWebviewMs, 10);
164
- if (isNaN(waitForWebviewMs)) {
165
- waitForWebviewMs = 0;
166
- }
167
- }
168
- const curOpt = this.opts.fullContextList;
169
- this.opts.fullContextList = true;
170
- const timer = new support_1.timing.Timer().start();
52
+ const ctxs = [{ id: utils_1.NATIVE_WIN, view: {} }];
53
+ this.contexts = [utils_1.NATIVE_WIN];
54
+ for (const view of webviews) {
55
+ ctxs.push({ id: `${WEBVIEW_BASE}${view.id}`, view });
56
+ this.contexts.push(view.id.toString());
57
+ }
58
+ return ctxs;
59
+ }
60
+ /**
61
+ * @deprecated this method is not used anywhere and will be removed in the future
62
+ * @this {XCUITestDriver}
63
+ * @returns {boolean}
64
+ */
65
+ function useNewSafari() {
66
+ return this.isSimulator() && this.isSafari();
67
+ }
68
+ /**
69
+ * @this {XCUITestDriver}
70
+ * @returns {Promise<void>}
71
+ */
72
+ async function activateRecentWebview() {
73
+ this.log.debug('Activating a recent webview');
74
+ const timer = new support_1.timing.Timer().start();
75
+ const contextId = await this.getRecentWebviewContextId(/.*/, /.*/);
76
+ if (contextId) {
77
+ this.log.info(`Picking webview '${contextId}' after ${timer.getDuration().asMilliSeconds.toFixed(0)}ms`);
78
+ await this.setContext(contextId);
79
+ return;
80
+ }
81
+ const appDict = ( /** @type {RemoteDebugger} */(this.remote)).appDict;
82
+ const errSuffix = `Make sure your web application is debuggable ` +
83
+ `and could be inspected in Safari Web Inspector.`;
84
+ if (lodash_1.default.isEmpty(appDict)) {
85
+ throw new Error(`The remote debugger did not return any connected web applications after ` +
86
+ `${timer.getDuration().asMilliSeconds.toFixed(0)}ms. ` +
87
+ `${errSuffix} ` +
88
+ `You may try to change the 'webviewConnectTimeout' capability value to ` +
89
+ `customize the retrieval timeout.`);
90
+ }
91
+ const errSuffix2 = `${errSuffix} You may try to change the 'webviewConnectRetries' ` +
92
+ `capability value to customize the amount of pages retrieval retries.`;
93
+ const appsWithPages = lodash_1.default.values(appDict).filter(({ pageArray }) => !lodash_1.default.isEmpty(pageArray));
94
+ if (appsWithPages.length > 0) {
95
+ throw new Error(`The remote debugger returned ${support_1.util.pluralize('web application', appsWithPages.length, true)} ` +
96
+ `with pages after ${timer.getDuration().asMilliSeconds.toFixed(0)}ms, ` +
97
+ `although none of them matched our page search criteria. ${errSuffix2}`);
98
+ }
99
+ else {
100
+ throw new Error(`The remote debugger returned ${support_1.util.pluralize('web application', lodash_1.default.size(appDict), true)}, ` +
101
+ `but none of them had pages after ${timer.getDuration().asMilliSeconds.toFixed(0)}ms. ` +
102
+ `${errSuffix2} Also, in rare cases the device restart or device OS upgrade may fix this ` +
103
+ `issue if none of the above advices helps.`);
104
+ }
105
+ }
106
+ /**
107
+ * @this {XCUITestDriver}
108
+ * @returns {Promise<import('../types').Page[]>}
109
+ */
110
+ async function listWebFrames(useUrl = true) {
111
+ const shouldFilterByUrl = useUrl && !this.isRealDevice() && !!this.getCurrentUrl();
112
+ this.log.debug(`Selecting by url: ${shouldFilterByUrl}` +
113
+ (shouldFilterByUrl ? ` (expected url: '${this.getCurrentUrl()}')` : ''));
114
+ if (!this.remote) {
115
+ await this.connectToRemoteDebugger();
116
+ }
117
+ const doListPages = async (/** @type {number} */ retries) => {
171
118
  try {
172
- /** @type {FullContext[]} */
173
- let contexts;
174
- do {
175
- contexts = /** @type {FullContext[]} */ (await this.getContexts());
176
- if (contexts.length >= 2) {
177
- this.log.debug(`Found webview context after ${timer.getDuration().asMilliSeconds.toFixed(0)}ms`);
178
- return contexts;
179
- }
180
- this.log.debug(`No webviews found in ${timer.getDuration().asMilliSeconds.toFixed(0)}ms`);
181
- } while (timer.getDuration().asMilliSeconds < waitForWebviewMs);
182
- return contexts;
183
- }
184
- finally {
185
- // reset the option so there are no side effects
186
- this.opts.fullContextList = curOpt;
187
- }
188
- },
189
- /**
190
- * @this {XCUITestDriver}
191
- * @param {import('./types').PageChangeNotification} pageChangeNotification
192
- * @returns {Promise<void>}
193
- */
194
- async onPageChange(pageChangeNotification) {
195
- this.log.debug(`Remote debugger notified us of a new page listing: ${JSON.stringify(pageChangeNotification)}`);
196
- if (this.selectingNewPage) {
197
- this.log.debug('We are in the middle of selecting a page, ignoring');
198
- return;
199
- }
200
- if (!this.remote?.isConnected) {
201
- this.log.debug('We have not yet connected, ignoring');
202
- return;
203
- }
204
- const { appIdKey, pageArray } = pageChangeNotification;
205
- /** @type {string[]} */
206
- const newIds = [];
207
- /** @type {string[]} */
208
- const newPages = [];
209
- /** @type {string|null} */
210
- let keyId = null;
211
- for (const page of pageArray) {
212
- const id = page.id.toString();
213
- newIds.push(id);
214
- if (page.isKey) {
215
- keyId = id;
119
+ const pageArray = await ( /** @type {RemoteDebugger} */(this.remote)).selectApp(shouldFilterByUrl ? this.getCurrentUrl() : undefined, retries, this.opts.ignoreAboutBlankUrl);
120
+ if (lodash_1.default.isEmpty(pageArray)) {
121
+ // we have no web frames, but continue anyway
122
+ this.log.debug(`No web frames found after ${support_1.util.pluralize('retry', retries, true)}`);
216
123
  }
217
- const contextId = `${appIdKey}.${id}`;
218
- // add if this is a new page
219
- if (!lodash_1.default.includes(this.contexts, contextId)) {
220
- if (isUrlIgnored(page.url, this.opts.safariIgnoreWebHostnames)) {
221
- this.log.info(`Not tracking '${page.url}' page because it is blacklisted. ` +
222
- `'safariIgnoreWebHostnames'=${this.opts.safariIgnoreWebHostnames}`);
223
- }
224
- else {
225
- newPages.push(id);
226
- this.contexts.push(contextId);
227
- }
228
- }
229
- }
230
- if (!keyId) {
231
- // if there is no key id, pull the first id from the page array and use that
232
- // as a stand in
233
- this.log.debug('No key id found. Choosing first id from page array');
234
- keyId = newIds[0] || null;
124
+ return pageArray;
235
125
  }
236
- if (!support_1.util.hasValue(this.curContext)) {
237
- this.log.debug('We do not appear to have window set yet, ignoring');
238
- return;
239
- }
240
- const [curAppIdKey, curPageIdKey] = this.curContext.split('.');
241
- if (curAppIdKey !== appIdKey) {
242
- this.log.debug('Page change not referring to currently selected app, ignoring.');
243
- return;
244
- }
245
- /** @type {string|null} */
246
- let newPage = null;
247
- if (newPages.length) {
248
- newPage = /** @type {string} */ (lodash_1.default.last(newPages));
249
- this.log.debug(`We have new pages, selecting page '${newPage}'`);
250
- }
251
- else if (!lodash_1.default.includes(newIds, curPageIdKey)) {
252
- this.log.debug('New page listing from remote debugger does not contain ' +
253
- 'current window; assuming it is closed');
254
- if (!support_1.util.hasValue(keyId)) {
255
- this.log.error('Do not have our current window anymore, and there ' +
256
- 'are not any more to load! Doing nothing...');
257
- this.setCurrentUrl(undefined);
258
- return;
259
- }
260
- this.log.debug(`Debugger already selected page '${keyId}', ` + `confirming that choice.`);
261
- this.curContext = `${appIdKey}.${keyId}`;
262
- newPage = keyId;
263
- }
264
- else {
265
- // at this point, there are no new pages, and the current page still exists
266
- this.log.debug('New page listing is same as old, doing nothing');
126
+ catch (err) {
127
+ this.log.debug(`No available web pages after ${support_1.util.pluralize('retry', retries, true)}: ${err.message}`);
128
+ return [];
129
+ }
130
+ };
131
+ /** @type {number} */
132
+ const maxRetriesCount = lodash_1.default.isInteger(this.opts.webviewConnectRetries)
133
+ ? Math.max(/** @type {number} */ (this.opts.webviewConnectRetries), 1)
134
+ : DEFAULT_LIST_WEB_FRAMES_RETRIES;
135
+ this.log.debug(`About to select a web application with ${support_1.util.pluralize('retry', maxRetriesCount, true)} ` +
136
+ `and 500ms interval between each retry. Consider customizing the value of 'webviewConnectRetries' ` +
137
+ `capability to change the amount of retries.`);
138
+ return await doListPages(maxRetriesCount);
139
+ }
140
+ /**
141
+ * @this {XCUITestDriver}
142
+ * @returns {Promise<void>}
143
+ */
144
+ async function connectToRemoteDebugger() {
145
+ this.remote = await this.getNewRemoteDebugger();
146
+ // @ts-ignore static is fine
147
+ this.remote.on(appium_remote_debugger_1.RemoteDebugger.EVENT_PAGE_CHANGE, this.onPageChange.bind(this));
148
+ // @ts-ignore static is fine
149
+ this.remote.on(appium_remote_debugger_1.RemoteDebugger.EVENT_FRAMES_DETACHED, () => {
150
+ if (!lodash_1.default.isEmpty(this.curWebFrames)) {
151
+ const curWebFrames = this.curWebFrames;
152
+ this.log.debug(`Clearing ${support_1.util.pluralize('frame', curWebFrames.length, true)}: ${curWebFrames.join(', ')}`);
267
153
  }
268
- // make sure that the page listing isn't indicating a redirect
269
- if (support_1.util.hasValue(this.curContext)) {
270
- const currentPageId = parseInt(String(lodash_1.default.last(this.curContext.split('.'))), 10);
271
- const page = lodash_1.default.find(pageArray, (p) => parseInt(String(p.id), 10) === currentPageId);
272
- if (page && page.url !== this.getCurrentUrl()) {
273
- this.log.debug(`Redirected from '${this.getCurrentUrl()}' to '${page.url}'`);
274
- this.setCurrentUrl(page.url);
275
- }
154
+ this.curWebFrames = [];
155
+ });
156
+ const timeoutMs = this.opts.webviewConnectTimeout ?? DEFAULT_REMOTE_DEBUGGER_CONNECT_TIMEOUT_MS;
157
+ const apps = await this.remote.connect(timeoutMs);
158
+ if (lodash_1.default.isEmpty(apps)) {
159
+ this.log.info(`The remote debugger did not report any active web applications within ${timeoutMs}ms timeout. ` +
160
+ `Consider increasing the value of 'webviewConnectTimeout' capability to wait longer ` +
161
+ `on slower devices.`);
162
+ }
163
+ }
164
+ /**
165
+ * Retrieves the list of available contexts.
166
+ *
167
+ * The list includes extended context information, like URLs and page names.
168
+ * This is different from the standard `getContexts` API, because the latter
169
+ * only has web view names without any additional information.
170
+ *
171
+ * @remarks In situations where multiple web views are available at once, the
172
+ * client code would have to connect to each of them in order to detect the
173
+ * one which needs to be interacted with. This extra effort is not needed with
174
+ * the information provided by this extension.
175
+ * @param {number} [waitForWebviewMs=0] - The period to poll for available webview(s) (in ms)
176
+ * @returns {Promise<Context[]>} The list of available context objects along with their properties.
177
+ * @this {XCUITestDriver}
178
+ */
179
+ async function mobileGetContexts(waitForWebviewMs = 0) {
180
+ // make sure it is a number, so the duration check works properly
181
+ if (!lodash_1.default.isNumber(waitForWebviewMs)) {
182
+ waitForWebviewMs = parseInt(waitForWebviewMs, 10);
183
+ if (isNaN(waitForWebviewMs)) {
184
+ waitForWebviewMs = 0;
276
185
  }
277
- if (support_1.util.hasValue(newPage)) {
278
- this.selectingNewPage = true;
279
- const oldContext = this.curContext;
280
- this.curContext = `${appIdKey}.${newPage}`;
281
- try {
282
- await this.remote.selectPage(appIdKey, parseInt(newPage, 10));
283
- await notifyBiDiContextChange.bind(this)();
284
- }
285
- catch (e) {
286
- this.log.warn(`Failed to select page: ${e.message}`);
287
- this.curContext = oldContext;
186
+ }
187
+ const curOpt = this.opts.fullContextList;
188
+ this.opts.fullContextList = true;
189
+ const timer = new support_1.timing.Timer().start();
190
+ try {
191
+ /** @type {FullContext[]} */
192
+ let contexts;
193
+ do {
194
+ contexts = /** @type {FullContext[]} */ (await this.getContexts());
195
+ if (contexts.length >= 2) {
196
+ this.log.debug(`Found webview context after ${timer.getDuration().asMilliSeconds.toFixed(0)}ms`);
197
+ return contexts;
288
198
  }
289
- finally {
290
- this.selectingNewPage = false;
199
+ this.log.debug(`No webviews found in ${timer.getDuration().asMilliSeconds.toFixed(0)}ms`);
200
+ } while (timer.getDuration().asMilliSeconds < waitForWebviewMs);
201
+ return contexts;
202
+ }
203
+ finally {
204
+ // reset the option so there are no side effects
205
+ this.opts.fullContextList = curOpt;
206
+ }
207
+ }
208
+ /**
209
+ * @this {XCUITestDriver}
210
+ * @param {import('./types').PageChangeNotification} pageChangeNotification
211
+ * @returns {Promise<void>}
212
+ */
213
+ async function onPageChange(pageChangeNotification) {
214
+ this.log.debug(`Remote debugger notified us of a new page listing: ${JSON.stringify(pageChangeNotification)}`);
215
+ if (this.selectingNewPage) {
216
+ this.log.debug('We are in the middle of selecting a page, ignoring');
217
+ return;
218
+ }
219
+ if (!this.remote?.isConnected) {
220
+ this.log.debug('We have not yet connected, ignoring');
221
+ return;
222
+ }
223
+ const { appIdKey, pageArray } = pageChangeNotification;
224
+ /** @type {string[]} */
225
+ const newIds = [];
226
+ /** @type {string[]} */
227
+ const newPages = [];
228
+ /** @type {string|null} */
229
+ let keyId = null;
230
+ for (const page of pageArray) {
231
+ const id = page.id.toString();
232
+ newIds.push(id);
233
+ if (page.isKey) {
234
+ keyId = id;
235
+ }
236
+ const contextId = `${appIdKey}.${id}`;
237
+ // add if this is a new page
238
+ if (!lodash_1.default.includes(this.contexts, contextId)) {
239
+ if (isUrlIgnored(page.url, this.opts.safariIgnoreWebHostnames)) {
240
+ this.log.info(`Not tracking '${page.url}' page because it is blacklisted. ` +
241
+ `'safariIgnoreWebHostnames'=${this.opts.safariIgnoreWebHostnames}`);
291
242
  }
292
- }
293
- },
294
- };
295
- const helpers = {
296
- /**
297
- * @this {XCUITestDriver}
298
- * @returns {Promise<void>}
299
- */
300
- async stopRemote(closeWindowBeforeDisconnecting = false) {
301
- if (!this.remote) {
302
- throw this.log.errorWithException('Tried to leave a web frame but were not in one');
303
- }
304
- if (closeWindowBeforeDisconnecting) {
305
- await this.closeWindow();
306
- }
307
- await this.remote.disconnect();
308
- this.curContext = null;
309
- await notifyBiDiContextChange.bind(this)();
310
- this.curWebFrames = [];
311
- this.remote = null;
312
- },
313
- /**
314
- * @this {XCUITestDriver}
315
- * @param {string|undefined|null} url
316
- */
317
- setCurrentUrl(url) {
318
- this._currentUrl = url;
319
- },
320
- /**
321
- * @this {XCUITestDriver}
322
- * @returns {string|undefined|null}
323
- */
324
- getCurrentUrl() {
325
- return this._currentUrl;
326
- },
327
- /**
328
- * @param {RegExp} titleRegExp
329
- * @param {RegExp} urlRegExp
330
- * @this {XCUITestDriver}
331
- * @returns {Promise<string|undefined>}
332
- */
333
- async getRecentWebviewContextId(titleRegExp, urlRegExp) {
334
- if (!lodash_1.default.isRegExp(titleRegExp) && !lodash_1.default.isRegExp(urlRegExp)) {
335
- throw new driver_1.errors.InvalidArgumentError('A regular expression for either web view title or url must be provided');
336
- }
337
- const currentUrl = this.getCurrentUrl();
338
- const contexts = lodash_1.default.filter(await this.getContextsAndViews(false), 'view');
339
- // first try to match by current url
340
- if (currentUrl) {
341
- const ctx = contexts.find(({ view }) => (view?.url || '') === currentUrl);
342
- if (ctx) {
343
- return ctx.id;
243
+ else {
244
+ newPages.push(id);
245
+ this.contexts.push(contextId);
344
246
  }
345
247
  }
346
- // if not, try to match by regular expression
347
- return contexts.find(({ view }) => (view?.title && titleRegExp?.test(view.title)) || (view?.url && urlRegExp?.test(view.url)))?.id;
348
- },
349
- /**
350
- * @this {XCUITestDriver}
351
- * @returns {boolean}
352
- */
353
- isWebContext() {
354
- return !!this.curContext && this.curContext !== utils_1.NATIVE_WIN;
355
- },
356
- /**
357
- * @this {XCUITestDriver}
358
- * @returns {boolean}
359
- */
360
- isWebview() {
361
- return this.isWebContext();
362
- },
363
- /**
364
- * @this {XCUITestDriver}
365
- * @returns {Promise<RemoteDebugger>}
366
- */
367
- async getNewRemoteDebugger() {
368
- const socketPath = this.isRealDevice()
369
- ? undefined
370
- : (await /** @type {import('../driver').Simulator} */ (this.device).getWebInspectorSocket() ?? undefined);
371
- return (0, appium_remote_debugger_1.createRemoteDebugger)({
372
- bundleId: this.opts.bundleId,
373
- additionalBundleIds: this.opts.additionalWebviewBundleIds,
374
- isSafari: this.isSafari(),
375
- includeSafari: this.opts.includeSafariInWebviews,
376
- pageLoadMs: this.pageLoadMs,
377
- platformVersion: this.opts.platformVersion,
378
- socketPath,
379
- remoteDebugProxy: this.opts.remoteDebugProxy,
380
- garbageCollectOnExecute: support_1.util.hasValue(this.opts.safariGarbageCollect)
381
- ? !!this.opts.safariGarbageCollect
382
- : false,
383
- udid: this.opts.udid,
384
- logAllCommunication: this.opts.safariLogAllCommunication,
385
- logAllCommunicationHexDump: this.opts.safariLogAllCommunicationHexDump,
386
- socketChunkSize: this.opts.safariSocketChunkSize,
387
- webInspectorMaxFrameLength: this.opts.safariWebInspectorMaxFrameLength,
388
- pageLoadStrategy: this.caps.pageLoadStrategy,
389
- }, this.isRealDevice());
390
- },
391
- };
392
- const commands = {
393
- /**
394
- * @this {XCUITestDriver}
395
- * @returns {Promise<string>}
396
- */
397
- async getCurrentContext() {
398
- if (this.curContext && this.curContext !== utils_1.NATIVE_WIN) {
399
- return `${WEBVIEW_BASE}${this.curContext}`;
400
- }
401
- return utils_1.NATIVE_WIN;
402
- },
403
- /**
404
- * Set context
405
- *
406
- * @param {string|Context} name - The name of context to set. It could be 'null' as NATIVE_WIN.
407
- * @param {any} [callback] The callback. (It is not called in this method)
408
- * @param {boolean} [skipReadyCheck=false] - Whether it waits for the new context is ready
409
- * @this {XCUITestDriver}
410
- * @returns {Promise<void>}
411
- */
412
- async setContext(name, callback, skipReadyCheck = false) {
413
- function alreadyInContext(desired, current) {
414
- return (desired === current ||
415
- (desired === null && current === utils_1.NATIVE_WIN) ||
416
- (desired === utils_1.NATIVE_WIN && current === null));
417
- }
418
- function isNativeContext(context) {
419
- return context === utils_1.NATIVE_WIN || context === null;
420
- }
421
- // allow the full context list to be passed in
422
- const strName = String(typeof name === 'object' && name.id ? name.id : name);
423
- this.log.debug(`Attempting to set context to '${strName || utils_1.NATIVE_WIN}' from '${this.curContext ? this.curContext : utils_1.NATIVE_WIN}'`);
424
- if (alreadyInContext(strName, this.curContext) ||
425
- alreadyInContext(lodash_1.default.replace(strName, WEBVIEW_BASE, ''), this.curContext)) {
426
- // already in the named context, no need to do anything
427
- this.log.debug(`Already in '${strName || utils_1.NATIVE_WIN}' context. Doing nothing.`);
428
- return;
429
- }
430
- if (isNativeContext(strName)) {
431
- // switching into the native context
432
- this.curContext = null;
433
- await notifyBiDiContextChange.bind(this)();
248
+ }
249
+ if (!keyId) {
250
+ // if there is no key id, pull the first id from the page array and use that
251
+ // as a stand in
252
+ this.log.debug('No key id found. Choosing first id from page array');
253
+ keyId = newIds[0] || null;
254
+ }
255
+ if (!support_1.util.hasValue(this.curContext)) {
256
+ this.log.debug('We do not appear to have window set yet, ignoring');
257
+ return;
258
+ }
259
+ const [curAppIdKey, curPageIdKey] = this.curContext.split('.');
260
+ if (curAppIdKey !== appIdKey) {
261
+ this.log.debug('Page change not referring to currently selected app, ignoring.');
262
+ return;
263
+ }
264
+ /** @type {string|null} */
265
+ let newPage = null;
266
+ if (newPages.length) {
267
+ newPage = /** @type {string} */ (lodash_1.default.last(newPages));
268
+ this.log.debug(`We have new pages, selecting page '${newPage}'`);
269
+ }
270
+ else if (!lodash_1.default.includes(newIds, curPageIdKey)) {
271
+ this.log.debug('New page listing from remote debugger does not contain ' +
272
+ 'current window; assuming it is closed');
273
+ if (!support_1.util.hasValue(keyId)) {
274
+ this.log.error('Do not have our current window anymore, and there ' +
275
+ 'are not any more to load! Doing nothing...');
276
+ this.setCurrentUrl(undefined);
434
277
  return;
435
278
  }
436
- // switching into a webview context
437
- // if contexts have not already been retrieved, get them
438
- if (lodash_1.default.isUndefined(this.contexts)) {
439
- await this.getContexts();
440
- }
441
- let contextId = lodash_1.default.replace(strName, WEBVIEW_BASE, '');
442
- if (contextId === '') {
443
- // allow user to pass in "WEBVIEW" without an index
444
- // the second context will be the first webview as
445
- // the first is always NATIVE_APP
446
- contextId = /** @type {string[]} */ (this.contexts)[1];
447
- }
448
- if (!lodash_1.default.includes(this.contexts, contextId)) {
449
- throw new driver_1.errors.NoSuchContextError();
279
+ this.log.debug(`Debugger already selected page '${keyId}', ` + `confirming that choice.`);
280
+ this.curContext = `${appIdKey}.${keyId}`;
281
+ newPage = keyId;
282
+ }
283
+ else {
284
+ // at this point, there are no new pages, and the current page still exists
285
+ this.log.debug('New page listing is same as old, doing nothing');
286
+ }
287
+ // make sure that the page listing isn't indicating a redirect
288
+ if (support_1.util.hasValue(this.curContext)) {
289
+ const currentPageId = parseInt(String(lodash_1.default.last(this.curContext.split('.'))), 10);
290
+ const page = lodash_1.default.find(pageArray, (p) => parseInt(String(p.id), 10) === currentPageId);
291
+ if (page && page.url !== this.getCurrentUrl()) {
292
+ this.log.debug(`Redirected from '${this.getCurrentUrl()}' to '${page.url}'`);
293
+ this.setCurrentUrl(page.url);
450
294
  }
295
+ }
296
+ if (support_1.util.hasValue(newPage)) {
297
+ this.selectingNewPage = true;
451
298
  const oldContext = this.curContext;
452
- this.curContext = this.curWindowHandle = contextId;
453
- // `contextId` will be in the form of `appId.pageId` in this case
454
- const [appIdKey, pageIdKey] = lodash_1.default.map(contextId.split('.'), (id) => parseInt(id, 10));
299
+ this.curContext = `${appIdKey}.${newPage}`;
455
300
  try {
456
- this.selectingNewPage = true;
457
- await ( /** @type {RemoteDebugger} */(this.remote)).selectPage(appIdKey, pageIdKey, skipReadyCheck);
301
+ await this.remote.selectPage(appIdKey, parseInt(newPage, 10));
458
302
  await notifyBiDiContextChange.bind(this)();
459
303
  }
460
- catch (err) {
461
- this.curContext = this.curWindowHandle = oldContext;
462
- throw err;
304
+ catch (e) {
305
+ this.log.warn(`Failed to select page: ${e.message}`);
306
+ this.curContext = oldContext;
463
307
  }
464
308
  finally {
465
309
  this.selectingNewPage = false;
466
310
  }
467
- // attempt to start performance logging, if requested
468
- if (this.opts.enablePerformanceLogging && this.remote) {
469
- const context = this.curContext;
470
- this.log.debug(`Starting performance log on '${context}'`);
471
- [this.logs.performance,] = log_1.assignBiDiLogListener.bind(this)(new ios_performance_log_1.IOSPerformanceLog({
472
- remoteDebugger: this.remote,
473
- log: this.log,
474
- }), {
475
- type: 'performance',
476
- context,
477
- });
478
- await this.logs.performance?.startCapture();
479
- }
480
- // start safari logging if the logs handlers are active
481
- if (name && name !== utils_1.NATIVE_WIN && this.logs) {
482
- if (this.logs.safariConsole) {
483
- ( /** @type {RemoteDebugger} */(this.remote)).startConsole(this.logs.safariConsole.onConsoleLogEvent.bind(this.logs.safariConsole));
484
- }
485
- if (this.logs.safariNetwork) {
486
- ( /** @type {RemoteDebugger} */(this.remote)).startNetwork(this.logs.safariNetwork.onNetworkEvent.bind(this.logs.safariNetwork));
487
- }
488
- }
489
- },
490
- /**
491
- * @this {XCUITestDriver}
492
- * @returns {Promise<string[]|FullContext[]>}
493
- */
494
- async getContexts() {
495
- this.log.debug('Getting list of available contexts');
496
- const contexts = await this.getContextsAndViews(false);
497
- if (this.opts.fullContextList) {
498
- return /** @type {import('./types').FullContext[]} */ (contexts.map((context) => ({
499
- id: context.id.toString(),
500
- title: context.view?.title,
501
- url: context.view?.url,
502
- bundleId: context.view?.bundleId,
503
- })));
504
- }
505
- return /** @type {string[]} */ (contexts.map((context) => context.id.toString()));
506
- },
507
- /**
508
- * @this {XCUITestDriver}
509
- * @param {string} name
510
- * @param {boolean} [skipReadyCheck]
511
- * @returns {Promise<void>}
512
- */
513
- async setWindow(name, skipReadyCheck) {
514
- if (!this.isWebContext()) {
515
- // https://github.com/appium/appium/issues/20710
516
- return;
517
- }
518
- try {
519
- await this.setContext(name, lodash_1.default.noop, skipReadyCheck);
520
- }
521
- catch (err) {
522
- // translate the error in terms of windows
523
- throw (0, driver_1.isErrorType)(err, driver_1.errors.NoSuchContextError) ? new driver_1.errors.NoSuchWindowError() : err;
524
- }
525
- },
526
- /**
527
- * @this {XCUITestDriver}
528
- * @returns {Promise<string>}
529
- */
530
- async getWindowHandle() {
531
- if (!this.isWebContext()) {
532
- // https://github.com/appium/appium/issues/20710
533
- return DEFAULT_NATIVE_WINDOW_HANDLE;
311
+ }
312
+ }
313
+ /**
314
+ * @this {XCUITestDriver}
315
+ * @returns {Promise<void>}
316
+ */
317
+ async function stopRemote(closeWindowBeforeDisconnecting = false) {
318
+ if (!this.remote) {
319
+ throw this.log.errorWithException('Tried to leave a web frame but were not in one');
320
+ }
321
+ if (closeWindowBeforeDisconnecting) {
322
+ await this.closeWindow();
323
+ }
324
+ await this.remote.disconnect();
325
+ this.curContext = null;
326
+ await notifyBiDiContextChange.bind(this)();
327
+ this.curWebFrames = [];
328
+ this.remote = null;
329
+ }
330
+ /**
331
+ * @this {XCUITestDriver}
332
+ * @param {string|undefined|null} url
333
+ */
334
+ function setCurrentUrl(url) {
335
+ this._currentUrl = url;
336
+ }
337
+ /**
338
+ * @this {XCUITestDriver}
339
+ * @returns {string|undefined|null}
340
+ */
341
+ function getCurrentUrl() {
342
+ return this._currentUrl;
343
+ }
344
+ /**
345
+ * @param {RegExp} titleRegExp
346
+ * @param {RegExp} urlRegExp
347
+ * @this {XCUITestDriver}
348
+ * @returns {Promise<string|undefined>}
349
+ */
350
+ async function getRecentWebviewContextId(titleRegExp, urlRegExp) {
351
+ if (!lodash_1.default.isRegExp(titleRegExp) && !lodash_1.default.isRegExp(urlRegExp)) {
352
+ throw new driver_1.errors.InvalidArgumentError('A regular expression for either web view title or url must be provided');
353
+ }
354
+ const currentUrl = this.getCurrentUrl();
355
+ const contexts = lodash_1.default.filter(await this.getContextsAndViews(false), 'view');
356
+ // first try to match by current url
357
+ if (currentUrl) {
358
+ const ctx = contexts.find(({ view }) => (view?.url || '') === currentUrl);
359
+ if (ctx) {
360
+ return ctx.id;
534
361
  }
535
- if (!this.curContext) {
536
- throw new driver_1.errors.InvalidContextError();
362
+ }
363
+ // if not, try to match by regular expression
364
+ return contexts.find(({ view }) => (view?.title && titleRegExp?.test(view.title)) || (view?.url && urlRegExp?.test(view.url)))?.id;
365
+ }
366
+ /**
367
+ * @this {XCUITestDriver}
368
+ * @returns {boolean}
369
+ */
370
+ function isWebContext() {
371
+ return !!this.curContext && this.curContext !== utils_1.NATIVE_WIN;
372
+ }
373
+ /**
374
+ * @this {XCUITestDriver}
375
+ * @returns {boolean}
376
+ */
377
+ function isWebview() {
378
+ return this.isWebContext();
379
+ }
380
+ /**
381
+ * @this {XCUITestDriver}
382
+ * @returns {Promise<RemoteDebugger>}
383
+ */
384
+ async function getNewRemoteDebugger() {
385
+ const socketPath = this.isRealDevice()
386
+ ? undefined
387
+ : (await /** @type {import('../driver').Simulator} */ (this.device).getWebInspectorSocket() ?? undefined);
388
+ return (0, appium_remote_debugger_1.createRemoteDebugger)({
389
+ bundleId: this.opts.bundleId,
390
+ additionalBundleIds: this.opts.additionalWebviewBundleIds,
391
+ isSafari: this.isSafari(),
392
+ includeSafari: this.opts.includeSafariInWebviews,
393
+ pageLoadMs: this.pageLoadMs,
394
+ platformVersion: this.opts.platformVersion,
395
+ socketPath,
396
+ remoteDebugProxy: this.opts.remoteDebugProxy,
397
+ garbageCollectOnExecute: support_1.util.hasValue(this.opts.safariGarbageCollect)
398
+ ? !!this.opts.safariGarbageCollect
399
+ : false,
400
+ udid: this.opts.udid,
401
+ logAllCommunication: this.opts.safariLogAllCommunication,
402
+ logAllCommunicationHexDump: this.opts.safariLogAllCommunicationHexDump,
403
+ socketChunkSize: this.opts.safariSocketChunkSize,
404
+ webInspectorMaxFrameLength: this.opts.safariWebInspectorMaxFrameLength,
405
+ pageLoadStrategy: this.caps.pageLoadStrategy,
406
+ }, this.isRealDevice());
407
+ }
408
+ /**
409
+ * @this {XCUITestDriver}
410
+ * @returns {Promise<string>}
411
+ */
412
+ async function getCurrentContext() {
413
+ if (this.curContext && this.curContext !== utils_1.NATIVE_WIN) {
414
+ return `${WEBVIEW_BASE}${this.curContext}`;
415
+ }
416
+ return utils_1.NATIVE_WIN;
417
+ }
418
+ /**
419
+ * Set context
420
+ *
421
+ * @param {string|Context} name - The name of context to set. It could be 'null' as NATIVE_WIN.
422
+ * @param {any} [callback] The callback. (It is not called in this method)
423
+ * @param {boolean} [skipReadyCheck=false] - Whether it waits for the new context is ready
424
+ * @this {XCUITestDriver}
425
+ * @returns {Promise<void>}
426
+ */
427
+ async function setContext(name, callback, skipReadyCheck = false) {
428
+ function alreadyInContext(desired, current) {
429
+ return (desired === current ||
430
+ (desired === null && current === utils_1.NATIVE_WIN) ||
431
+ (desired === utils_1.NATIVE_WIN && current === null));
432
+ }
433
+ function isNativeContext(context) {
434
+ return context === utils_1.NATIVE_WIN || context === null;
435
+ }
436
+ // allow the full context list to be passed in
437
+ const strName = String(typeof name === 'object' && name.id ? name.id : name);
438
+ this.log.debug(`Attempting to set context to '${strName || utils_1.NATIVE_WIN}' from '${this.curContext ? this.curContext : utils_1.NATIVE_WIN}'`);
439
+ if (alreadyInContext(strName, this.curContext) ||
440
+ alreadyInContext(lodash_1.default.replace(strName, WEBVIEW_BASE, ''), this.curContext)) {
441
+ // already in the named context, no need to do anything
442
+ this.log.debug(`Already in '${strName || utils_1.NATIVE_WIN}' context. Doing nothing.`);
443
+ return;
444
+ }
445
+ if (isNativeContext(strName)) {
446
+ // switching into the native context
447
+ this.curContext = null;
448
+ await notifyBiDiContextChange.bind(this)();
449
+ return;
450
+ }
451
+ // switching into a webview context
452
+ // if contexts have not already been retrieved, get them
453
+ if (lodash_1.default.isUndefined(this.contexts)) {
454
+ await this.getContexts();
455
+ }
456
+ let contextId = lodash_1.default.replace(strName, WEBVIEW_BASE, '');
457
+ if (contextId === '') {
458
+ // allow user to pass in "WEBVIEW" without an index
459
+ // the second context will be the first webview as
460
+ // the first is always NATIVE_APP
461
+ contextId = /** @type {string[]} */ (this.contexts)[1];
462
+ }
463
+ if (!lodash_1.default.includes(this.contexts, contextId)) {
464
+ throw new driver_1.errors.NoSuchContextError();
465
+ }
466
+ const oldContext = this.curContext;
467
+ this.curContext = this.curWindowHandle = contextId;
468
+ // `contextId` will be in the form of `appId.pageId` in this case
469
+ const [appIdKey, pageIdKey] = lodash_1.default.map(contextId.split('.'), (id) => parseInt(id, 10));
470
+ try {
471
+ this.selectingNewPage = true;
472
+ await ( /** @type {RemoteDebugger} */(this.remote)).selectPage(appIdKey, pageIdKey, skipReadyCheck);
473
+ await notifyBiDiContextChange.bind(this)();
474
+ }
475
+ catch (err) {
476
+ this.curContext = this.curWindowHandle = oldContext;
477
+ throw err;
478
+ }
479
+ finally {
480
+ this.selectingNewPage = false;
481
+ }
482
+ // attempt to start performance logging, if requested
483
+ if (this.opts.enablePerformanceLogging && this.remote) {
484
+ const context = this.curContext;
485
+ this.log.debug(`Starting performance log on '${context}'`);
486
+ [this.logs.performance,] = log_1.assignBiDiLogListener.bind(this)(new ios_performance_log_1.IOSPerformanceLog({
487
+ remoteDebugger: this.remote,
488
+ log: this.log,
489
+ }), {
490
+ type: 'performance',
491
+ context,
492
+ });
493
+ await this.logs.performance?.startCapture();
494
+ }
495
+ // start safari logging if the logs handlers are active
496
+ if (name && name !== utils_1.NATIVE_WIN && this.logs) {
497
+ if (this.logs.safariConsole) {
498
+ ( /** @type {RemoteDebugger} */(this.remote)).startConsole(this.logs.safariConsole.onConsoleLogEvent.bind(this.logs.safariConsole));
537
499
  }
538
- this.log.debug(`Getting current window handle`);
539
- return this.curContext;
540
- },
541
- /**
542
- * @this {XCUITestDriver}
543
- * @returns {Promise<string[]>}
544
- */
545
- async getWindowHandles() {
546
- if (!this.isWebContext()) {
547
- // https://github.com/appium/appium/issues/20710
548
- return [DEFAULT_NATIVE_WINDOW_HANDLE];
500
+ if (this.logs.safariNetwork) {
501
+ ( /** @type {RemoteDebugger} */(this.remote)).startNetwork(this.logs.safariNetwork.onNetworkEvent.bind(this.logs.safariNetwork));
549
502
  }
550
- this.log.debug('Getting list of available window handles');
551
- const contexts = await this.getContextsAndViews(false);
552
- return (contexts
553
- // get rid of the native app context
554
- .filter((context) => context.id !== utils_1.NATIVE_WIN)
555
- // get the `app.id` format expected
556
- .map((context) =>
557
- // This is non-nullable because the `FullContext` having `id` `NATIVE_WIN`
558
- // _looks like_ the only with an empty view.
559
- context.view?.id?.toString() ?? ''));
560
- },
561
- };
503
+ }
504
+ }
505
+ /**
506
+ * @this {XCUITestDriver}
507
+ * @returns {Promise<string[]|FullContext[]>}
508
+ */
509
+ async function getContexts() {
510
+ this.log.debug('Getting list of available contexts');
511
+ const contexts = await this.getContextsAndViews(false);
512
+ if (this.opts.fullContextList) {
513
+ return /** @type {import('./types').FullContext[]} */ (contexts.map((context) => ({
514
+ id: context.id.toString(),
515
+ title: context.view?.title,
516
+ url: context.view?.url,
517
+ bundleId: context.view?.bundleId,
518
+ })));
519
+ }
520
+ return /** @type {string[]} */ (contexts.map((context) => context.id.toString()));
521
+ }
522
+ /**
523
+ * @this {XCUITestDriver}
524
+ * @param {string} name
525
+ * @param {boolean} [skipReadyCheck]
526
+ * @returns {Promise<void>}
527
+ */
528
+ async function setWindow(name, skipReadyCheck) {
529
+ if (!this.isWebContext()) {
530
+ // https://github.com/appium/appium/issues/20710
531
+ return;
532
+ }
533
+ try {
534
+ await this.setContext(name, lodash_1.default.noop, skipReadyCheck);
535
+ }
536
+ catch (err) {
537
+ // translate the error in terms of windows
538
+ throw (0, driver_1.isErrorType)(err, driver_1.errors.NoSuchContextError) ? new driver_1.errors.NoSuchWindowError() : err;
539
+ }
540
+ }
541
+ /**
542
+ * @this {XCUITestDriver}
543
+ * @returns {Promise<string>}
544
+ */
545
+ async function getWindowHandle() {
546
+ if (!this.isWebContext()) {
547
+ // https://github.com/appium/appium/issues/20710
548
+ return DEFAULT_NATIVE_WINDOW_HANDLE;
549
+ }
550
+ if (!this.curContext) {
551
+ throw new driver_1.errors.InvalidContextError();
552
+ }
553
+ this.log.debug(`Getting current window handle`);
554
+ return this.curContext;
555
+ }
556
+ /**
557
+ * @this {XCUITestDriver}
558
+ * @returns {Promise<string[]>}
559
+ */
560
+ async function getWindowHandles() {
561
+ if (!this.isWebContext()) {
562
+ // https://github.com/appium/appium/issues/20710
563
+ return [DEFAULT_NATIVE_WINDOW_HANDLE];
564
+ }
565
+ this.log.debug('Getting list of available window handles');
566
+ const contexts = await this.getContextsAndViews(false);
567
+ return (contexts
568
+ // get rid of the native app context
569
+ .filter((context) => context.id !== utils_1.NATIVE_WIN)
570
+ // get the `app.id` format expected
571
+ .map((context) =>
572
+ // This is non-nullable because the `FullContext` having `id` `NATIVE_WIN`
573
+ // _looks like_ the only with an empty view.
574
+ context.view?.id?.toString() ?? ''));
575
+ }
562
576
  /**
563
577
  * Checks if a URL is blacklisted in the 'safariIgnoreWebHostnames' capability
564
578
  *
@@ -603,7 +617,6 @@ async function notifyBiDiContextChange() {
603
617
  this.eventEmitter.emit(constants_1.BIDI_EVENT_NAME, (0, models_1.makeContextUpdatedEvent)(name));
604
618
  this.eventEmitter.emit(constants_1.BIDI_EVENT_NAME, (0, models_1.makeObsoleteContextUpdatedEvent)(name));
605
619
  }
606
- exports.default = { ...helpers, ...extensions, ...commands };
607
620
  /**
608
621
  * @typedef {import('../driver').XCUITestDriver} XCUITestDriver
609
622
  * @typedef {import('./types').Context} Context