appium-xcuitest-driver 10.3.0 → 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 (252) hide show
  1. package/CHANGELOG.md +6 -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 +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 +319 -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 +878 -858
  237. package/lib/commands/xctest-record-screen.js +103 -105
  238. package/lib/commands/xctest.js +134 -139
  239. package/lib/driver.js +287 -235
  240. package/lib/execute-method-map.ts +9 -0
  241. package/npm-shrinkwrap.json +2 -2
  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/lib/commands/activeAppInfo.js +0 -14
  252. package/lib/commands/index.js +0 -95
@@ -57,6 +57,232 @@ const Alert = /** @type {const} */ ({
57
57
  },
58
58
  });
59
59
 
60
+ const LIBRE_SSL_PATTERN = /\/CN=([^\/]+)/; // eslint-disable-line no-useless-escape
61
+ const OPEN_SSL_PATTERN = /,\sCN\s=\s([^,]+)/;
62
+
63
+ /**
64
+ * Parses the common name of the certificate from the given string.
65
+ *
66
+ * @param {string} stringCertificate
67
+ * @returns {string} The common name of the certificate
68
+ */
69
+ export function parseCommonName(stringCertificate) {
70
+ const result = [LIBRE_SSL_PATTERN, OPEN_SSL_PATTERN].reduce((acc, r) => {
71
+ if (acc) {
72
+ return acc;
73
+ }
74
+ const match = r.exec(stringCertificate);
75
+ return match?.[1];
76
+ }, null);
77
+ if (!result) {
78
+ throw new Error(`There is no common name value in '${stringCertificate}' output`);
79
+ }
80
+ return result;
81
+ }
82
+
83
+ /**
84
+ * Installs a custom certificate onto the device.
85
+ *
86
+ * Since Xcode SDK 11.4, Apple has added a dedicated `simctl` subcommand to quickly handle
87
+ * certificates on Simulator over CLI.
88
+ *
89
+ * On real devices (or simulators before Xcode SDK 11.4), Apple provides no "official" way to do this via the command line. In such a case (and also as a fallback if CLI setup fails), this method tries to wrap the certificate into `.mobileconfig` format, then deploys the wrapped file to the internal HTTP server so that it can be opened via mobile Safari. This command then goes through the profile installation procedure by clicking the necessary buttons using WebDriverAgent.
90
+ * @param {string} content - Base64-encoded content of the public certificate in [PEM](https://knowledge.digicert.com/quovadis/ssl-certificates/ssl-general-topics/what-is-pem-format.html) format
91
+ * @param {string} [commonName] - Common name of the certificate. If this is not set, the command will try to parse it from the provided `content`.
92
+ * @param {boolean} isRoot - Defines where the certificate should be installed; either the Trusted Root Store (`true`) or the Keychain (`false`). On environments other than Xcode 11.4+ Simulator, this option is ignored.
93
+ * @returns {Promise<string|void>} The content of the generated `.mobileconfig` file as
94
+ * a base64-encoded string. This config might be useful for debugging purposes. If the certificate has been successfully set via CLI, then nothing is returned.
95
+ * @this {XCUITestDriver}
96
+ */
97
+ export async function mobileInstallCertificate(content, commonName, isRoot = true) {
98
+ if (_.isEmpty(content)) {
99
+ throw new Error('Certificate content should not be empty');
100
+ }
101
+
102
+ if (this.isSimulator()) {
103
+ try {
104
+ const methodName = isRoot ? 'addRootCertificate' : 'addCertificate';
105
+ await /** @type {import('../driver').Simulator} */ (this.device).simctl[methodName](Buffer.from(content, 'base64').toString(), {
106
+ raw: true,
107
+ });
108
+ return;
109
+ } catch (e) {
110
+ this.log.debug(e);
111
+ this.log.info(
112
+ `The certificate cannot be installed via CLI. ` + `Falling back to UI-based deployment`,
113
+ );
114
+ }
115
+ } else {
116
+ const client = new Pyidevice({
117
+ udid: this.opts.udid,
118
+ log: this.log,
119
+ });
120
+ if (await client.assertExists(false)) {
121
+ await client.installProfile({payload: Buffer.from(content, 'base64')});
122
+ return;
123
+ } else {
124
+ this.log.info(
125
+ 'pyidevice is not installed on your system. ' +
126
+ 'Falling back to the (slow) UI-based installation',
127
+ );
128
+ }
129
+ }
130
+
131
+ const tmpRoot = await tempDir.openDir();
132
+ const tmpPort = await findAPortNotInUse(HOST_PORT_RANGE[0], HOST_PORT_RANGE[1]);
133
+ const configName = `appium.${CONFIG_EXTENSION}`;
134
+ const configPath = path.resolve(tmpRoot, configName);
135
+ const tmpServer = http.createServer(async function (_, res) {
136
+ const configFile = await fs.readFile(configPath);
137
+ res.end(configFile);
138
+ });
139
+ try {
140
+ const certBuffer = Buffer.from(content, 'base64');
141
+ const cn = commonName || (await extractCommonName(certBuffer));
142
+ const mobileConfig = toMobileConfig(certBuffer, cn);
143
+ try {
144
+ await plist.updatePlistFile(configPath, mobileConfig, false, false);
145
+ } catch (err) {
146
+ throw new Error(
147
+ `Cannot store the generated config as '${configPath}'. ` +
148
+ `Original error: ${err.message}`,
149
+ );
150
+ }
151
+
152
+ try {
153
+ const host = os.hostname();
154
+ const certUrl = `http://${host}:${tmpPort}/${configName}`;
155
+ await tmpServer.listen(tmpPort);
156
+ try {
157
+ await waitForCondition(
158
+ async () => {
159
+ try {
160
+ return (await checkPortStatus(tmpPort, host)) === 'open';
161
+ } catch {
162
+ return false;
163
+ }
164
+ },
165
+ {
166
+ waitMs: TMPSERVER_STARTUP_TIMEOUT,
167
+ intervalMs: 300,
168
+ },
169
+ );
170
+ this.log.debug(`The temporary web server is running at http://${host}:${tmpPort}`);
171
+ } catch {
172
+ throw new Error(
173
+ `The temporary web server cannot be started at http://${host}:${tmpPort}.`,
174
+ );
175
+ }
176
+ if (this.isRealDevice()) {
177
+ try {
178
+ await this.proxyCommand('/url', 'POST', {url: certUrl});
179
+ } catch (err) {
180
+ if (this.isWebContext()) {
181
+ // The command above does not always work on real devices
182
+ await this.setUrl(certUrl);
183
+ } else {
184
+ throw err;
185
+ }
186
+ }
187
+ } else {
188
+ await /** @type {import('../driver').Simulator} */ (this.device).openUrl(certUrl);
189
+ }
190
+
191
+ let isCertAlreadyInstalled = false;
192
+ if (util.compareVersions(/** @type {string} */ (this.opts.platformVersion), '>=', '12.2')) {
193
+ if (await installPost122Certificate(this, cn)) {
194
+ await clickElement(this, Settings.Profile);
195
+ await trustCertificateInPreferences(this, cn);
196
+ } else {
197
+ isCertAlreadyInstalled = true;
198
+ }
199
+ } else {
200
+ if (await installPre122Certificate(this)) {
201
+ await clickElement(this, Button.Return_to_Settings);
202
+ await trustCertificateInPreferences(this, cn);
203
+ } else {
204
+ isCertAlreadyInstalled = true;
205
+ }
206
+ }
207
+ if (isCertAlreadyInstalled) {
208
+ this.log.info(
209
+ `It looks like the '${cn}' certificate has been already added to the CA root`,
210
+ );
211
+ }
212
+ } finally {
213
+ if (this.opts.bundleId) {
214
+ try {
215
+ await this.activateApp(this.opts.bundleId);
216
+ } catch (e) {
217
+ this.log.warn(
218
+ `Cannot restore the application '${this.opts.bundleId}'. Original error: ${e.message}`,
219
+ );
220
+ }
221
+ }
222
+ }
223
+
224
+ return (await util.toInMemoryBase64(configPath)).toString();
225
+ } finally {
226
+ await tmpServer.close();
227
+ await fs.rimraf(tmpRoot);
228
+ }
229
+ }
230
+
231
+ /**
232
+ * Removes installed certificates.
233
+ *
234
+ * This only works _if and only if_ `py-ios-device` is installed on the same machine Appium is running on.
235
+ *
236
+ * @see https://github.com/YueChen-C/py-ios-device
237
+ * @since 4.19.2
238
+ * @param {string} name - Name of the profile
239
+ * @returns {Promise<string>} Returns status acknowledgment status if
240
+ * tht certificate is successfully removed or 'None' (basically just
241
+ * forwards the original pyidevice output)
242
+ * @throws {Error} If attempting to remove certificates for a simulated device or if `py-ios-device` is not installed
243
+ * @group Real Device Only
244
+ */
245
+ export async function mobileRemoveCertificate(name) {
246
+ if (!this.isRealDevice()) {
247
+ throw new errors.NotImplementedError('This extension is only supported on real devices');
248
+ }
249
+ const client = new Pyidevice({
250
+ udid: this.opts.udid,
251
+ log: this.log,
252
+ });
253
+ await client.assertExists(true);
254
+ return await client.removeProfile(name);
255
+ }
256
+
257
+ /**
258
+ * Returns map of certificates installed on the real device.
259
+ *
260
+ * This only works _if and only if_ `py-ios-device` is installed on the same machine Appium is running on.
261
+ * @since 4.10.0
262
+ * @see https://github.com/YueChen-C/py-ios-device
263
+ * @returns {Promise<import('./types').CertificateList>} An object describing the certificates installed on the real device.
264
+ * @throws {Error} If attempting to list certificates for a simulated device or if `py-ios-device` is not installed
265
+ * @this {XCUITestDriver}
266
+ */
267
+ export async function mobileListCertificates() {
268
+ if (!this.isRealDevice()) {
269
+ throw new errors.NotImplementedError('This extension is only supported on real devices');
270
+ }
271
+ const client = new Pyidevice({
272
+ udid: this.opts.udid,
273
+ log: this.log,
274
+ });
275
+ await client.assertExists(true);
276
+ return await client.listProfiles();
277
+ }
278
+
279
+
280
+ /**
281
+ * Extracts the common name of the certificate from the given buffer.
282
+ *
283
+ * @param {Buffer} certBuffer
284
+ * @returns {Promise<string>} The common name of the certificate
285
+ */
60
286
  async function extractCommonName(certBuffer) {
61
287
  const tempCert = await tempDir.open({
62
288
  prefix: 'cert',
@@ -76,23 +302,6 @@ async function extractCommonName(certBuffer) {
76
302
  }
77
303
  }
78
304
 
79
- const LIBRE_SSL_PATTERN = /\/CN=([^\/]+)/; // eslint-disable-line no-useless-escape
80
- const OPEN_SSL_PATTERN = /,\sCN\s=\s([^,]+)/;
81
-
82
- export function parseCommonName(stringCertificate) {
83
- const result = [LIBRE_SSL_PATTERN, OPEN_SSL_PATTERN].reduce((acc, r) => {
84
- if (acc) {
85
- return acc;
86
- }
87
- const match = r.exec(stringCertificate);
88
- return match?.[1];
89
- }, null);
90
- if (!result) {
91
- throw new Error(`There is no common name value in '${stringCertificate}' output`);
92
- }
93
- return result;
94
- }
95
-
96
305
  /**
97
306
  * Generates Apple's over-the-air configuration profile
98
307
  * for certificate deployment based on the given PEM certificate content.
@@ -283,204 +492,6 @@ async function installPost122Certificate(driver, name) {
283
492
  return true;
284
493
  }
285
494
 
286
- export default {
287
- /**
288
- * Installs a custom certificate onto the device.
289
- *
290
- * Since Xcode SDK 11.4, Apple has added a dedicated `simctl` subcommand to quickly handle
291
- * certificates on Simulator over CLI.
292
- *
293
- * On real devices (or simulators before Xcode SDK 11.4), Apple provides no "official" way to do this via the command line. In such a case (and also as a fallback if CLI setup fails), this method tries to wrap the certificate into `.mobileconfig` format, then deploys the wrapped file to the internal HTTP server so that it can be opened via mobile Safari. This command then goes through the profile installation procedure by clicking the necessary buttons using WebDriverAgent.
294
- * @param {string} content - Base64-encoded content of the public certificate in [PEM](https://knowledge.digicert.com/quovadis/ssl-certificates/ssl-general-topics/what-is-pem-format.html) format
295
- * @param {string} [commonName] - Common name of the certificate. If this is not set, the command will try to parse it from the provided `content`.
296
- * @param {boolean} isRoot - Defines where the certificate should be installed; either the Trusted Root Store (`true`) or the Keychain (`false`). On environments other than Xcode 11.4+ Simulator, this option is ignored.
297
- * @returns {Promise<string|void>} The content of the generated `.mobileconfig` file as
298
- * a base64-encoded string. This config might be useful for debugging purposes. If the certificate has been successfully set via CLI, then nothing is returned.
299
- * @this {XCUITestDriver}
300
- */
301
- async mobileInstallCertificate(content, commonName, isRoot = true) {
302
- if (_.isEmpty(content)) {
303
- throw new Error('Certificate content should not be empty');
304
- }
305
-
306
- if (this.isSimulator()) {
307
- try {
308
- const methodName = isRoot ? 'addRootCertificate' : 'addCertificate';
309
- await /** @type {import('../driver').Simulator} */ (this.device).simctl[methodName](Buffer.from(content, 'base64').toString(), {
310
- raw: true,
311
- });
312
- return;
313
- } catch (e) {
314
- this.log.debug(e);
315
- this.log.info(
316
- `The certificate cannot be installed via CLI. ` + `Falling back to UI-based deployment`,
317
- );
318
- }
319
- } else {
320
- const client = new Pyidevice({
321
- udid: this.opts.udid,
322
- log: this.log,
323
- });
324
- if (await client.assertExists(false)) {
325
- await client.installProfile({payload: Buffer.from(content, 'base64')});
326
- return;
327
- } else {
328
- this.log.info(
329
- 'pyidevice is not installed on your system. ' +
330
- 'Falling back to the (slow) UI-based installation',
331
- );
332
- }
333
- }
334
-
335
- const tmpRoot = await tempDir.openDir();
336
- const tmpPort = await findAPortNotInUse(HOST_PORT_RANGE[0], HOST_PORT_RANGE[1]);
337
- const configName = `appium.${CONFIG_EXTENSION}`;
338
- const configPath = path.resolve(tmpRoot, configName);
339
- const tmpServer = http.createServer(async function (_, res) {
340
- const configFile = await fs.readFile(configPath);
341
- res.end(configFile);
342
- });
343
- try {
344
- const certBuffer = Buffer.from(content, 'base64');
345
- const cn = commonName || (await extractCommonName(certBuffer));
346
- const mobileConfig = toMobileConfig(certBuffer, cn);
347
- try {
348
- await plist.updatePlistFile(configPath, mobileConfig, false, false);
349
- } catch (err) {
350
- throw new Error(
351
- `Cannot store the generated config as '${configPath}'. ` +
352
- `Original error: ${err.message}`,
353
- );
354
- }
355
-
356
- try {
357
- const host = os.hostname();
358
- const certUrl = `http://${host}:${tmpPort}/${configName}`;
359
- await tmpServer.listen(tmpPort);
360
- try {
361
- await waitForCondition(
362
- async () => {
363
- try {
364
- return (await checkPortStatus(tmpPort, host)) === 'open';
365
- } catch {
366
- return false;
367
- }
368
- },
369
- {
370
- waitMs: TMPSERVER_STARTUP_TIMEOUT,
371
- intervalMs: 300,
372
- },
373
- );
374
- this.log.debug(`The temporary web server is running at http://${host}:${tmpPort}`);
375
- } catch {
376
- throw new Error(
377
- `The temporary web server cannot be started at http://${host}:${tmpPort}.`,
378
- );
379
- }
380
- if (this.isRealDevice()) {
381
- try {
382
- await this.proxyCommand('/url', 'POST', {url: certUrl});
383
- } catch (err) {
384
- if (this.isWebContext()) {
385
- // The command above does not always work on real devices
386
- await this.setUrl(certUrl);
387
- } else {
388
- throw err;
389
- }
390
- }
391
- } else {
392
- await /** @type {import('../driver').Simulator} */ (this.device).openUrl(certUrl);
393
- }
394
-
395
- let isCertAlreadyInstalled = false;
396
- if (util.compareVersions(/** @type {string} */ (this.opts.platformVersion), '>=', '12.2')) {
397
- if (await installPost122Certificate(this, cn)) {
398
- await clickElement(this, Settings.Profile);
399
- await trustCertificateInPreferences(this, cn);
400
- } else {
401
- isCertAlreadyInstalled = true;
402
- }
403
- } else {
404
- if (await installPre122Certificate(this)) {
405
- await clickElement(this, Button.Return_to_Settings);
406
- await trustCertificateInPreferences(this, cn);
407
- } else {
408
- isCertAlreadyInstalled = true;
409
- }
410
- }
411
- if (isCertAlreadyInstalled) {
412
- this.log.info(
413
- `It looks like the '${cn}' certificate has been already added to the CA root`,
414
- );
415
- }
416
- } finally {
417
- if (this.opts.bundleId) {
418
- try {
419
- await this.activateApp(this.opts.bundleId);
420
- } catch (e) {
421
- this.log.warn(
422
- `Cannot restore the application '${this.opts.bundleId}'. Original error: ${e.message}`,
423
- );
424
- }
425
- }
426
- }
427
-
428
- return (await util.toInMemoryBase64(configPath)).toString();
429
- } finally {
430
- await tmpServer.close();
431
- await fs.rimraf(tmpRoot);
432
- }
433
- },
434
-
435
- /**
436
- * Removes installed certificates.
437
- *
438
- * This only works _if and only if_ `py-ios-device` is installed on the same machine Appium is running on.
439
- *
440
- * @see https://github.com/YueChen-C/py-ios-device
441
- * @since 4.19.2
442
- * @param {string} name - Name of the profile
443
- * @returns {Promise<string>} Returns status acknowledgment status if
444
- * tht certificate is successfully removed or 'None' (basically just
445
- * forwards the original pyidevice output)
446
- * @throws {Error} If attempting to remove certificates for a simulated device or if `py-ios-device` is not installed
447
- * @group Real Device Only
448
- */
449
- async mobileRemoveCertificate(name) {
450
- if (!this.isRealDevice()) {
451
- throw new errors.NotImplementedError('This extension is only supported on real devices');
452
- }
453
- const client = new Pyidevice({
454
- udid: this.opts.udid,
455
- log: this.log,
456
- });
457
- await client.assertExists(true);
458
- return await client.removeProfile(name);
459
- },
460
-
461
- /**
462
- * Returns map of certificates installed on the real device.
463
- *
464
- * This only works _if and only if_ `py-ios-device` is installed on the same machine Appium is running on.
465
- * @since 4.10.0
466
- * @see https://github.com/YueChen-C/py-ios-device
467
- * @returns {Promise<import('./types').CertificateList>} An object describing the certificates installed on the real device.
468
- * @throws {Error} If attempting to list certificates for a simulated device or if `py-ios-device` is not installed
469
- * @this {XCUITestDriver}
470
- */
471
- async mobileListCertificates() {
472
- if (!this.isRealDevice()) {
473
- throw new errors.NotImplementedError('This extension is only supported on real devices');
474
- }
475
- const client = new Pyidevice({
476
- udid: this.opts.udid,
477
- log: this.log,
478
- });
479
- await client.assertExists(true);
480
- return await client.listProfiles();
481
- },
482
- };
483
-
484
495
  /**
485
496
  * @typedef {import('../driver').XCUITestDriver} XCUITestDriver
486
497
  */
@@ -1,36 +1,34 @@
1
- export default {
2
- /**
3
- * Sets the primary clipboard's content on the device under test.
4
- *
5
- * @param {string} content - The content to be set as base64 encoded string.
6
- * @param {string} [contentType=plaintext] - The type of the content to set.
7
- * Only `plaintext`, 'image and 'url' are supported.
8
- * @this {XCUITestDriver}
9
- */
10
- async setClipboard(content, contentType) {
11
- await this.proxyCommand('/wda/setPasteboard', 'POST', {
12
- content,
13
- contentType,
14
- });
15
- },
1
+ /**
2
+ * Sets the primary clipboard's content on the device under test.
3
+ *
4
+ * @param {string} content - The content to be set as base64 encoded string.
5
+ * @param {string} [contentType=plaintext] - The type of the content to set.
6
+ * Only `plaintext`, 'image and 'url' are supported.
7
+ * @this {XCUITestDriver}
8
+ */
9
+ export async function setClipboard(content, contentType) {
10
+ await this.proxyCommand('/wda/setPasteboard', 'POST', {
11
+ content,
12
+ contentType,
13
+ });
14
+ }
16
15
 
17
- /**
18
- * Gets the content of the primary clipboard on the device under test.
19
- *
20
- * @param {string} [contentType=plaintext] - The type of the content to get.
21
- * Only `plaintext`, 'image and 'url' are supported.
22
- * @returns {Promise<string>} The actual clipboard content encoded into base64 string.
23
- * An empty string is returned if the clipboard contains no data.
24
- * @this {XCUITestDriver}
25
- */
26
- async getClipboard(contentType) {
27
- return /** @type {string} */ (
28
- await this.proxyCommand('/wda/getPasteboard', 'POST', {
29
- contentType,
30
- })
31
- );
32
- },
33
- };
16
+ /**
17
+ * Gets the content of the primary clipboard on the device under test.
18
+ *
19
+ * @param {string} [contentType=plaintext] - The type of the content to get.
20
+ * Only `plaintext`, 'image and 'url' are supported.
21
+ * @returns {Promise<string>} The actual clipboard content encoded into base64 string.
22
+ * An empty string is returned if the clipboard contains no data.
23
+ * @this {XCUITestDriver}
24
+ */
25
+ export async function getClipboard(contentType) {
26
+ return /** @type {string} */ (
27
+ await this.proxyCommand('/wda/getPasteboard', 'POST', {
28
+ contentType,
29
+ })
30
+ );
31
+ }
34
32
 
35
33
  /**
36
34
  * @typedef {import('../driver').XCUITestDriver} XCUITestDriver