appium-xcuitest-driver 10.8.3 → 10.8.4

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 (66) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/build/lib/commands/app-management.js +1 -1
  3. package/build/lib/commands/app-management.js.map +1 -1
  4. package/build/lib/commands/file-movement.d.ts.map +1 -1
  5. package/build/lib/commands/file-movement.js +4 -4
  6. package/build/lib/commands/file-movement.js.map +1 -1
  7. package/build/lib/commands/memory.js +1 -1
  8. package/build/lib/commands/memory.js.map +1 -1
  9. package/build/lib/commands/performance.d.ts.map +1 -1
  10. package/build/lib/commands/performance.js +13 -4
  11. package/build/lib/commands/performance.js.map +1 -1
  12. package/build/lib/commands/xctest-record-screen.js +1 -1
  13. package/build/lib/commands/xctest-record-screen.js.map +1 -1
  14. package/build/lib/device/device-connections-factory.d.ts +18 -0
  15. package/build/lib/device/device-connections-factory.d.ts.map +1 -0
  16. package/build/lib/{device-connections-factory.js → device/device-connections-factory.js} +57 -41
  17. package/build/lib/device/device-connections-factory.js.map +1 -0
  18. package/build/lib/device/real-device-management.d.ts +146 -0
  19. package/build/lib/device/real-device-management.d.ts.map +1 -0
  20. package/build/lib/device/real-device-management.js +727 -0
  21. package/build/lib/device/real-device-management.js.map +1 -0
  22. package/build/lib/device/simulator-management.d.ts +65 -0
  23. package/build/lib/device/simulator-management.d.ts.map +1 -0
  24. package/build/lib/{simulator-management.js → device/simulator-management.js} +23 -42
  25. package/build/lib/device/simulator-management.js.map +1 -0
  26. package/build/lib/driver.d.ts +1 -1
  27. package/build/lib/driver.d.ts.map +1 -1
  28. package/build/lib/driver.js +5 -6
  29. package/build/lib/driver.js.map +1 -1
  30. package/lib/commands/app-management.js +1 -1
  31. package/lib/commands/file-movement.js +8 -4
  32. package/lib/commands/memory.js +1 -1
  33. package/lib/commands/performance.js +12 -1
  34. package/lib/commands/xctest-record-screen.js +1 -1
  35. package/lib/{device-connections-factory.js → device/device-connections-factory.ts} +96 -60
  36. package/lib/device/real-device-management.ts +818 -0
  37. package/lib/{simulator-management.js → device/simulator-management.ts} +68 -61
  38. package/lib/driver.js +3 -5
  39. package/npm-shrinkwrap.json +2 -2
  40. package/package.json +1 -1
  41. package/build/lib/device-connections-factory.d.ts +0 -13
  42. package/build/lib/device-connections-factory.d.ts.map +0 -1
  43. package/build/lib/device-connections-factory.js.map +0 -1
  44. package/build/lib/ios-fs-helpers.d.ts +0 -75
  45. package/build/lib/ios-fs-helpers.d.ts.map +0 -1
  46. package/build/lib/ios-fs-helpers.js +0 -370
  47. package/build/lib/ios-fs-helpers.js.map +0 -1
  48. package/build/lib/real-device-management.d.ts +0 -53
  49. package/build/lib/real-device-management.d.ts.map +0 -1
  50. package/build/lib/real-device-management.js +0 -128
  51. package/build/lib/real-device-management.js.map +0 -1
  52. package/build/lib/real-device.d.ts +0 -112
  53. package/build/lib/real-device.d.ts.map +0 -1
  54. package/build/lib/real-device.js +0 -352
  55. package/build/lib/real-device.js.map +0 -1
  56. package/build/lib/simulator-management.d.ts +0 -96
  57. package/build/lib/simulator-management.d.ts.map +0 -1
  58. package/build/lib/simulator-management.js.map +0 -1
  59. package/build/lib/xcrun.d.ts +0 -3
  60. package/build/lib/xcrun.d.ts.map +0 -1
  61. package/build/lib/xcrun.js +0 -17
  62. package/build/lib/xcrun.js.map +0 -1
  63. package/lib/ios-fs-helpers.js +0 -355
  64. package/lib/real-device-management.js +0 -133
  65. package/lib/real-device.js +0 -347
  66. package/lib/xcrun.js +0 -16
@@ -1,355 +0,0 @@
1
- import _ from 'lodash';
2
- import B, {TimeoutError} from 'bluebird';
3
- import {fs, tempDir, mkdirp, zip, util, timing} from 'appium/support';
4
- import path from 'path';
5
- import log from './logger';
6
-
7
- export const IO_TIMEOUT_MS = 4 * 60 * 1000;
8
- // Mobile devices use NAND memory modules for the storage,
9
- // and the parallelism there is not as performant as on regular SSDs
10
- const MAX_IO_CHUNK_SIZE = 8;
11
-
12
- /**
13
- * Retrieve a file from a real device
14
- *
15
- * @param {any} afcService Apple File Client service instance from
16
- * 'appium-ios-device' module
17
- * @param {string} remotePath Relative path to the file on the device
18
- * @returns {Promise<Buffer>} The file content as a buffer
19
- */
20
- export async function pullFile(afcService, remotePath) {
21
- const stream = await afcService.createReadStream(remotePath, {autoDestroy: true});
22
- const pullPromise = new B((resolve, reject) => {
23
- stream.on('close', resolve);
24
- stream.on('error', reject);
25
- }).timeout(IO_TIMEOUT_MS);
26
- const buffers = [];
27
- stream.on('data', (data) => buffers.push(data));
28
- await pullPromise;
29
- return Buffer.concat(buffers);
30
- }
31
-
32
- /**
33
- * Checks a presence of a local folder.
34
- *
35
- * @param {string} folderPath Full path to the local folder
36
- * @returns {Promise<boolean>} True if the folder exists and is actually a folder
37
- */
38
- async function folderExists(folderPath) {
39
- try {
40
- return (await fs.stat(folderPath)).isDirectory();
41
- } catch {
42
- return false;
43
- }
44
- }
45
-
46
- /**
47
- * Retrieve a folder from a real device
48
- *
49
- * @param {any} afcService Apple File Client service instance from
50
- * 'appium-ios-device' module
51
- * @param {string} remoteRootPath Relative path to the folder on the device
52
- * @returns {Promise<Buffer>} The folder content as a zipped base64-encoded buffer
53
- */
54
- export async function pullFolder(afcService, remoteRootPath) {
55
- const tmpFolder = await tempDir.openDir();
56
- try {
57
- let localTopItem = null;
58
- let countFilesSuccess = 0;
59
- let countFilesFail = 0;
60
- let countFolders = 0;
61
- const pullPromises = [];
62
- await afcService.walkDir(remoteRootPath, true, async (remotePath, isDir) => {
63
- const localPath = path.join(tmpFolder, remotePath);
64
- const dirname = isDir ? localPath : path.dirname(localPath);
65
- if (!(await folderExists(dirname))) {
66
- await mkdirp(dirname);
67
- }
68
- if (!localTopItem || localPath.split(path.sep).length < localTopItem.split(path.sep).length) {
69
- localTopItem = localPath;
70
- }
71
- if (isDir) {
72
- ++countFolders;
73
- return;
74
- }
75
-
76
- const readStream = await afcService.createReadStream(remotePath, {autoDestroy: true});
77
- const writeStream = fs.createWriteStream(localPath, {autoClose: true});
78
- pullPromises.push(
79
- new B((resolve) => {
80
- writeStream.on('close', () => {
81
- ++countFilesSuccess;
82
- resolve();
83
- });
84
- const onStreamingError = (e) => {
85
- readStream.unpipe(writeStream);
86
- log.warn(
87
- `Cannot pull '${remotePath}' to '${localPath}'. ` +
88
- `The file will be skipped. Original error: ${e.message}`,
89
- );
90
- ++countFilesFail;
91
- resolve();
92
- };
93
- writeStream.on('error', onStreamingError);
94
- readStream.on('error', onStreamingError);
95
- }).timeout(IO_TIMEOUT_MS),
96
- );
97
- readStream.pipe(writeStream);
98
- if (pullPromises.length >= MAX_IO_CHUNK_SIZE) {
99
- await B.any(pullPromises);
100
- for (let i = pullPromises.length - 1; i >= 0; i--) {
101
- if (pullPromises[i].isFulfilled()) {
102
- pullPromises.splice(i, 1);
103
- }
104
- }
105
- }
106
- });
107
- // Wait for the rest of files to be pulled
108
- if (!_.isEmpty(pullPromises)) {
109
- await B.all(pullPromises);
110
- }
111
- log.info(
112
- `Pulled ${util.pluralize('file', countFilesSuccess, true)} out of ` +
113
- `${countFilesSuccess + countFilesFail} and ${util.pluralize(
114
- 'folder',
115
- countFolders,
116
- true,
117
- )} ` +
118
- `from '${remoteRootPath}'`,
119
- );
120
- return await zip.toInMemoryZip(localTopItem ? path.dirname(localTopItem) : tmpFolder, {
121
- encodeToBase64: true,
122
- });
123
- } finally {
124
- await fs.rimraf(tmpFolder);
125
- }
126
- }
127
-
128
- /**
129
- * Creates remote folder path recursively. Noop if the given path
130
- * already exists
131
- *
132
- * @param {any} afcService Apple File Client service instance from
133
- * 'appium-ios-device' module
134
- * @param {string} remoteRoot The relative path to the remote folder structure
135
- * to be created
136
- */
137
- async function remoteMkdirp(afcService, remoteRoot) {
138
- if (remoteRoot === '.' || remoteRoot === '/') {
139
- return;
140
- }
141
- try {
142
- await afcService.listDirectory(remoteRoot);
143
- return;
144
- } catch {
145
- // This means that the directory is missing and we got an object not found error.
146
- // Therefore, we are going to the parent
147
- await remoteMkdirp(afcService, path.dirname(remoteRoot));
148
- }
149
- await afcService.createDirectory(remoteRoot);
150
- }
151
-
152
- /**
153
- * @typedef {Object} PushFileOptions
154
- * @property {number} [timeoutMs=240000] The maximum count of milliceconds to wait until
155
- * file push is completed. Cannot be lower than 60000ms
156
- */
157
-
158
- /**
159
- * Pushes a file to a real device
160
- *
161
- * @param {any} afcService afcService Apple File Client service instance from
162
- * 'appium-ios-device' module
163
- * @param {string|Buffer} localPathOrPayload Either full path to the source file
164
- * or a buffer payload to be written into the remote destination
165
- * @param {string} remotePath Relative path to the file on the device. The remote
166
- * folder structure is created automatically if necessary.
167
- * @param {PushFileOptions} [opts={}]
168
- */
169
- export async function pushFile(afcService, localPathOrPayload, remotePath, opts = {}) {
170
- const {timeoutMs = IO_TIMEOUT_MS} = opts;
171
- const timer = new timing.Timer().start();
172
- await remoteMkdirp(afcService, path.dirname(remotePath));
173
- const source = Buffer.isBuffer(localPathOrPayload)
174
- ? localPathOrPayload
175
- : fs.createReadStream(localPathOrPayload, {autoClose: true});
176
- const writeStream = await afcService.createWriteStream(remotePath, {
177
- autoDestroy: true,
178
- });
179
- writeStream.on('finish', writeStream.destroy);
180
- let pushError = null;
181
- const filePushPromise = new B((resolve, reject) => {
182
- writeStream.on('close', () => {
183
- if (pushError) {
184
- reject(pushError);
185
- } else {
186
- resolve();
187
- }
188
- });
189
- const onStreamError = (e) => {
190
- if (!Buffer.isBuffer(source)) {
191
- source.unpipe(writeStream);
192
- }
193
- log.debug(e);
194
- pushError = e;
195
- };
196
- writeStream.on('error', onStreamError);
197
- if (!Buffer.isBuffer(source)) {
198
- source.on('error', onStreamError);
199
- }
200
- });
201
- if (Buffer.isBuffer(source)) {
202
- writeStream.write(source);
203
- writeStream.end();
204
- } else {
205
- source.pipe(writeStream);
206
- }
207
- await filePushPromise.timeout(Math.max(timeoutMs, 60000));
208
- const fileSize = Buffer.isBuffer(localPathOrPayload)
209
- ? localPathOrPayload.length
210
- : (await fs.stat(localPathOrPayload)).size;
211
- log.debug(
212
- `Successfully pushed the file payload (${util.toReadableSizeString(fileSize)}) ` +
213
- `to the remote location '${remotePath}' in ${timer.getDuration().asMilliSeconds.toFixed(0)}ms`,
214
- );
215
- }
216
-
217
- /**
218
- * @typedef {Object} PushFolderOptions
219
- *
220
- * @property {number} [timeoutMs=240000] The maximum timeout to wait until a
221
- * single file is copied
222
- * @property {boolean} [enableParallelPush=false] Whether to push files in parallel.
223
- * This usually gives better performance, but might sometimes be less stable.
224
- */
225
-
226
- /**
227
- * Pushes a folder to a real device
228
- *
229
- * @param {any} afcService Apple File Client service instance from
230
- * 'appium-ios-device' module
231
- * @param {string} srcRootPath The full path to the source folder
232
- * @param {string} dstRootPath The relative path to the destination folder. The folder
233
- * will be deleted if already exists.
234
- * @param {PushFolderOptions} opts
235
- */
236
- export async function pushFolder(afcService, srcRootPath, dstRootPath, opts = {}) {
237
- const {timeoutMs = IO_TIMEOUT_MS, enableParallelPush = false} = opts;
238
-
239
- const timer = new timing.Timer().start();
240
- const allItems = /** @type {import('path-scurry').Path[]} */ (
241
- /** @type {unknown} */ (
242
- await fs.glob('**', {
243
- cwd: srcRootPath,
244
- withFileTypes: true,
245
- })
246
- )
247
- );
248
- log.debug(`Successfully scanned the tree structure of '${srcRootPath}'`);
249
- // top-level folders go first
250
- /** @type {string[]} */
251
- const foldersToPush = allItems
252
- .filter((x) => x.isDirectory())
253
- .map((x) => x.relative())
254
- .sort((a, b) => a.split(path.sep).length - b.split(path.sep).length);
255
- // larger files go first
256
- /** @type {string[]} */
257
- const filesToPush = allItems
258
- .filter((x) => !x.isDirectory())
259
- .sort((a, b) => (b.size ?? 0) - (a.size ?? 0))
260
- .map((x) => x.relative());
261
- log.debug(
262
- `Got ${util.pluralize('folder', foldersToPush.length, true)} and ` +
263
- `${util.pluralize('file', filesToPush.length, true)} to push`,
264
- );
265
- // create the folder structure first
266
- try {
267
- await afcService.deleteDirectory(dstRootPath);
268
- } catch {}
269
- await afcService.createDirectory(dstRootPath);
270
- for (const relativeFolderPath of foldersToPush) {
271
- // createDirectory does not accept folder names ending with a path separator
272
- const absoluteFolderPath = _.trimEnd(path.join(dstRootPath, relativeFolderPath), path.sep);
273
- if (absoluteFolderPath) {
274
- await afcService.createDirectory(absoluteFolderPath);
275
- }
276
- }
277
- // do not forget about the root folder
278
- log.debug(
279
- `Successfully created the remote folder structure ` +
280
- `(${util.pluralize('item', foldersToPush.length + 1, true)})`,
281
- );
282
-
283
- const _pushFile = async (/** @type {string} */ relativePath) => {
284
- const absoluteSourcePath = path.join(srcRootPath, relativePath);
285
- const readStream = fs.createReadStream(absoluteSourcePath, {autoClose: true});
286
- const absoluteDestinationPath = path.join(dstRootPath, relativePath);
287
- const writeStream = await afcService.createWriteStream(absoluteDestinationPath, {
288
- autoDestroy: true,
289
- });
290
- writeStream.on('finish', writeStream.destroy);
291
- let pushError = null;
292
- const filePushPromise = new B((resolve, reject) => {
293
- writeStream.on('close', () => {
294
- if (pushError) {
295
- reject(pushError);
296
- } else {
297
- resolve();
298
- }
299
- });
300
- const onStreamError = (e) => {
301
- readStream.unpipe(writeStream);
302
- log.debug(e);
303
- pushError = e;
304
- };
305
- writeStream.on('error', onStreamError);
306
- readStream.on('error', onStreamError);
307
- });
308
- readStream.pipe(writeStream);
309
- await filePushPromise.timeout(Math.max(timeoutMs - timer.getDuration().asMilliSeconds, 60000));
310
- };
311
-
312
- if (enableParallelPush) {
313
- log.debug(`Proceeding to parallel files push (max ${MAX_IO_CHUNK_SIZE} writers)`);
314
- const pushPromises = [];
315
- for (const relativeFilePath of filesToPush) {
316
- pushPromises.push(B.resolve(_pushFile(relativeFilePath)));
317
- // keep the push queue filled
318
- if (pushPromises.length >= MAX_IO_CHUNK_SIZE) {
319
- await B.any(pushPromises);
320
- const elapsedMs = timer.getDuration().asMilliSeconds;
321
- if (elapsedMs > timeoutMs) {
322
- throw new TimeoutError(`Timed out after ${elapsedMs} ms`);
323
- }
324
- }
325
- for (let i = pushPromises.length - 1; i >= 0; i--) {
326
- if (pushPromises[i].isFulfilled()) {
327
- pushPromises.splice(i, 1);
328
- }
329
- }
330
- }
331
- if (!_.isEmpty(pushPromises)) {
332
- const remainingPromises = pushPromises.filter((p) => !p.isFulfilled());
333
- if (remainingPromises.length > 0) {
334
- await B.all(remainingPromises).timeout(
335
- Math.max(timeoutMs - timer.getDuration().asMilliSeconds, 60000),
336
- );
337
- }
338
- }
339
- } else {
340
- log.debug(`Proceeding to serial files push`);
341
- for (const relativeFilePath of filesToPush) {
342
- await _pushFile(relativeFilePath);
343
- const elapsedMs = timer.getDuration().asMilliSeconds;
344
- if (elapsedMs > timeoutMs) {
345
- throw new TimeoutError(`Timed out after ${elapsedMs} ms`);
346
- }
347
- }
348
- }
349
-
350
- log.debug(
351
- `Successfully pushed ${util.pluralize('folder', foldersToPush.length, true)} ` +
352
- `and ${util.pluralize('file', filesToPush.length, true)} ` +
353
- `within ${timer.getDuration().asMilliSeconds.toFixed(0)}ms`,
354
- );
355
- }
@@ -1,133 +0,0 @@
1
- import _ from 'lodash';
2
- import {buildSafariPreferences} from './app-utils';
3
- import { getConnectedDevices } from './real-device';
4
-
5
- const DEFAULT_APP_INSTALLATION_TIMEOUT_MS = 8 * 60 * 1000;
6
-
7
- /**
8
- * @typedef {Object} InstallOptions
9
- *
10
- * @property {boolean} [skipUninstall] Whether to skip app uninstall before installing it
11
- * @property {number} [timeout=480000] App install timeout
12
- * @property {boolean} [shouldEnforceUninstall] Whether to enforce the app uninstallation. e.g. fullReset, or enforceAppInstall is true
13
- */
14
-
15
- /**
16
- * @this {import('./driver').XCUITestDriver}
17
- * @param {string} [app] The app to the path
18
- * @param {string} [bundleId] The bundle id to ensure it is already installed and uninstall it
19
- * @param {InstallOptions} [opts={}]
20
- */
21
- export async function installToRealDevice(app, bundleId, opts = {}) {
22
- const device = /** @type {RealDevice} */ (this.device);
23
-
24
- if (!device.udid || !app || !bundleId) {
25
- this.log.debug('No device id, app or bundle id, not installing to real device.');
26
- return;
27
- }
28
-
29
- const {
30
- skipUninstall,
31
- timeout = DEFAULT_APP_INSTALLATION_TIMEOUT_MS,
32
- } = opts;
33
-
34
- if (!skipUninstall) {
35
- this.log.info(`Reset requested. Removing app with id '${bundleId}' from the device`);
36
- await device.remove(bundleId);
37
- }
38
- this.log.debug(`Installing '${app}' on the device with UUID '${device.udid}'`);
39
-
40
- try {
41
- await device.install(app, bundleId, {
42
- timeoutMs: timeout,
43
- });
44
- this.log.debug('The app has been installed successfully.');
45
- } catch (e) {
46
- // Want to clarify the device's application installation state in this situation.
47
-
48
- if (!skipUninstall || !e.message.includes('MismatchedApplicationIdentifierEntitlement')) {
49
- // Other error cases that could not be recoverable by here.
50
- // Exact error will be in the log.
51
-
52
- // We cannot recover 'ApplicationVerificationFailed' situation since this reason is clearly the app's provisioning profile was invalid.
53
- // [XCUITest] Error installing app '/path/to.app': Unexpected data: {"Error":"ApplicationVerificationFailed","ErrorDetail":-402620395,"ErrorDescription":"Failed to verify code signature of /path/to.app : 0xe8008015 (A valid provisioning profile for this executable was not found.)"}
54
- throw e;
55
- }
56
-
57
- // If the error was by below error case, we could recover the situation
58
- // by uninstalling the device's app bundle id explicitly regard less the app exists on the device or not (e.g. offload app).
59
- // [XCUITest] Error installing app '/path/to.app': Unexpected data: {"Error":"MismatchedApplicationIdentifierEntitlement","ErrorDescription":"Upgrade's application-identifier entitlement string (TEAM_ID.com.kazucocoa.example) does not match installed application's application-identifier string (ANOTHER_TEAM_ID.com.kazucocoa.example); rejecting upgrade."}
60
- this.log.info(`The application identified by '${bundleId}' cannot be installed because it might ` +
61
- `be already cached on the device, probably with a different signature. ` +
62
- `Will try to remove it and install a new copy. Original error: ${e.message}`);
63
- await device.remove(bundleId);
64
- await device.install(app, bundleId, {
65
- timeoutMs: timeout,
66
- });
67
- this.log.debug('The app has been installed after one retrial.');
68
- }
69
- }
70
-
71
- /**
72
- * @this {import('./driver').XCUITestDriver}
73
- * @returns {Promise<void>}
74
- */
75
- export async function runRealDeviceReset() {
76
- if (!this.opts.noReset || this.opts.fullReset) {
77
- this.log.debug('Reset: running ios real device reset flow');
78
- if (!this.opts.noReset) {
79
- await /** @type {RealDevice} */ (this.device).reset(this.opts);
80
- }
81
- } else {
82
- this.log.debug('Reset: fullReset not set. Leaving as is');
83
- }
84
- }
85
-
86
- /**
87
- * Configures Safari startup options based on the given session capabilities.
88
- *
89
- * !!! This method mutates driver options.
90
- *
91
- * @this {import('./driver').XCUITestDriver}
92
- * @return {boolean} true if process arguments have been modified
93
- */
94
- export function applySafariStartupArgs() {
95
- const prefs = buildSafariPreferences(this.opts);
96
- if (_.isEmpty(prefs)) {
97
- return false;
98
- }
99
-
100
- const args = _.toPairs(prefs)
101
- .flatMap(([key, value]) => [_.startsWith(key, '-') ? key : `-${key}`, String(value)]);
102
- this.log.debug(`Generated Safari command line arguments: ${args.join(' ')}`);
103
- if (_.isPlainObject(this.opts.processArguments)) {
104
- this.opts.processArguments.args = [...(this.opts.processArguments.args ?? []), ...args];
105
- } else {
106
- this.opts.processArguments = {args};
107
- }
108
- return true;
109
- }
110
-
111
- /**
112
- * @this {XCUITestDriver}
113
- * @returns {Promise<string>}
114
- */
115
- export async function detectUdid() {
116
- this.log.debug('Auto-detecting real device udid...');
117
- const udids = await getConnectedDevices();
118
- if (_.isEmpty(udids)) {
119
- throw new Error('No real devices are connected to the host');
120
- }
121
- const udid = udids[udids.length - 1];
122
- if (udids.length > 1) {
123
- this.log.info(`Multiple devices found: ${udids.join(', ')}`);
124
- this.log.info(`Choosing '${udid}'. Consider settings the 'udid' capability if another device must be selected`);
125
- }
126
- this.log.debug(`Detected real device udid: '${udid}'`);
127
- return udid;
128
- }
129
-
130
- /**
131
- * @typedef {import('./real-device').RealDevice} RealDevice}
132
- * @typedef {import('./driver').XCUITestDriver} XCUITestDriver
133
- */