appium-xcuitest-driver 10.14.13 → 10.15.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.
@@ -1,6 +1,6 @@
1
1
  import _ from 'lodash';
2
2
  import B, {TimeoutError} from 'bluebird';
3
- import {fs, tempDir, mkdirp, zip, util, timing} from 'appium/support';
3
+ import {fs, tempDir, zip, util, timing} from 'appium/support';
4
4
  import path from 'path';
5
5
  import {services, utilities, INSTRUMENT_CHANNEL} from 'appium-ios-device';
6
6
  import {buildSafariPreferences, SAFARI_BUNDLE_ID} from '../app-utils';
@@ -8,12 +8,13 @@ import {log as defaultLogger} from '../logger';
8
8
  import { Devicectl } from 'node-devicectl';
9
9
  import type { AppiumLogger } from '@appium/types';
10
10
  import type { XCUITestDriver } from '../driver';
11
+ import {AfcClient} from './afc-client';
11
12
 
12
13
  const DEFAULT_APP_INSTALLATION_TIMEOUT_MS = 8 * 60 * 1000;
13
14
  export const IO_TIMEOUT_MS = 4 * 60 * 1000;
14
15
  // Mobile devices use NAND memory modules for the storage,
15
16
  // and the parallelism there is not as performant as on regular SSDs
16
- const MAX_IO_CHUNK_SIZE = 8;
17
+ export const MAX_IO_CHUNK_SIZE = 8;
17
18
  const APPLICATION_INSTALLED_NOTIFICATION = 'com.apple.mobile.application_installed';
18
19
  const APPLICATION_NOTIFICATION_TIMEOUT_MS = 30 * 1000;
19
20
  const INSTALLATION_STAGING_DIR = 'PublicStaging';
@@ -23,96 +24,52 @@ const INSTALLATION_STAGING_DIR = 'PublicStaging';
23
24
  /**
24
25
  * Retrieve a file from a real device
25
26
  *
26
- * @param afcService Apple File Client service instance from
27
- * 'appium-ios-device' module
27
+ * @param client AFC client instance
28
28
  * @param remotePath Relative path to the file on the device
29
29
  * @returns The file content as a buffer
30
30
  */
31
- export async function pullFile(afcService: any, remotePath: string): Promise<Buffer> {
32
- const stream = await afcService.createReadStream(remotePath, {autoDestroy: true});
33
- const pullPromise = new B((resolve, reject) => {
34
- stream.on('close', resolve);
35
- stream.on('error', reject);
36
- }).timeout(IO_TIMEOUT_MS);
37
- const buffers: Buffer[] = [];
38
- stream.on('data', (data: Buffer) => buffers.push(data));
39
- await pullPromise;
40
- return Buffer.concat(buffers);
31
+ export async function pullFile(client: AfcClient, remotePath: string): Promise<Buffer> {
32
+ return await B.resolve(client.getFileContents(remotePath)).timeout(
33
+ IO_TIMEOUT_MS,
34
+ `Timed out after ${IO_TIMEOUT_MS}ms while pulling file from '${remotePath}'`
35
+ );
41
36
  }
42
37
 
43
38
  /**
44
39
  * Retrieve a folder from a real device
45
40
  *
46
- * @param afcService Apple File Client service instance from
47
- * 'appium-ios-device' module
41
+ * @param client AFC client instance
48
42
  * @param remoteRootPath Relative path to the folder on the device
49
43
  * @returns The folder content as a zipped base64-encoded buffer
50
44
  */
51
- export async function pullFolder(afcService: any, remoteRootPath: string): Promise<Buffer> {
45
+ export async function pullFolder(client: AfcClient, remoteRootPath: string): Promise<Buffer> {
52
46
  const tmpFolder = await tempDir.openDir();
53
47
  try {
54
48
  let localTopItem: string | null = null;
55
49
  let countFilesSuccess = 0;
56
- let countFilesFail = 0;
57
50
  let countFolders = 0;
58
- const pullPromises: B<void>[] = [];
59
- await afcService.walkDir(remoteRootPath, true, async (remotePath: string, isDir: boolean) => {
60
- const localPath = path.join(tmpFolder, remotePath);
61
- const dirname = isDir ? localPath : path.dirname(localPath);
62
- if (!(await folderExists(dirname))) {
63
- await mkdirp(dirname);
64
- }
65
- if (!localTopItem || localPath.split(path.sep).length < localTopItem.split(path.sep).length) {
66
- localTopItem = localPath;
67
- }
68
- if (isDir) {
69
- ++countFolders;
70
- return;
71
- }
72
51
 
73
- const readStream = await afcService.createReadStream(remotePath, {autoDestroy: true});
74
- const writeStream = fs.createWriteStream(localPath, {autoClose: true});
75
- pullPromises.push(
76
- new B<void>((resolve) => {
77
- writeStream.on('close', () => {
78
- ++countFilesSuccess;
79
- resolve();
80
- });
81
- const onStreamingError = (e: Error) => {
82
- readStream.unpipe(writeStream);
83
- defaultLogger.warn(
84
- `Cannot pull '${remotePath}' to '${localPath}'. ` +
85
- `The file will be skipped. Original error: ${e.message}`,
86
- );
87
- ++countFilesFail;
88
- resolve();
89
- };
90
- writeStream.on('error', onStreamingError);
91
- readStream.on('error', onStreamingError);
92
- }).timeout(IO_TIMEOUT_MS),
93
- );
94
- readStream.pipe(writeStream);
95
- if (pullPromises.length >= MAX_IO_CHUNK_SIZE) {
96
- await B.any(pullPromises);
97
- for (let i = pullPromises.length - 1; i >= 0; i--) {
98
- if (pullPromises[i].isFulfilled()) {
99
- pullPromises.splice(i, 1);
100
- }
52
+ await client.pull(remoteRootPath, tmpFolder, {
53
+ recursive: true,
54
+ overwrite: true,
55
+ onEntry: async (remotePath: string, localPath: string, isDirectory: boolean) => {
56
+ if (!localTopItem || localPath.split(path.sep).length < localTopItem.split(path.sep).length) {
57
+ localTopItem = localPath;
101
58
  }
102
- }
59
+ if (isDirectory) {
60
+ ++countFolders;
61
+ } else {
62
+ ++countFilesSuccess;
63
+ }
64
+ },
103
65
  });
104
- // Wait for the rest of files to be pulled
105
- if (!_.isEmpty(pullPromises)) {
106
- await B.all(pullPromises);
107
- }
66
+
108
67
  defaultLogger.info(
109
- `Pulled ${util.pluralize('file', countFilesSuccess, true)} out of ` +
110
- `${countFilesSuccess + countFilesFail} and ${util.pluralize(
111
- 'folder',
112
- countFolders,
113
- true,
114
- )} ` +
115
- `from '${remoteRootPath}'`,
68
+ `Pulled ${util.pluralize('file', countFilesSuccess, true)} and ${util.pluralize(
69
+ 'folder',
70
+ countFolders,
71
+ true,
72
+ )} from '${remoteRootPath}'`,
116
73
  );
117
74
  return await zip.toInMemoryZip(localTopItem ? path.dirname(localTopItem) : tmpFolder, {
118
75
  encodeToBase64: true,
@@ -125,8 +82,7 @@ export async function pullFolder(afcService: any, remoteRootPath: string): Promi
125
82
  /**
126
83
  * Pushes a file to a real device
127
84
  *
128
- * @param afcService afcService Apple File Client service instance from
129
- * 'appium-ios-device' module
85
+ * @param client AFC client instance
130
86
  * @param localPathOrPayload Either full path to the source file
131
87
  * or a buffer payload to be written into the remote destination
132
88
  * @param remotePath Relative path to the file on the device. The remote
@@ -134,49 +90,30 @@ export async function pullFolder(afcService: any, remoteRootPath: string): Promi
134
90
  * @param opts Push file options
135
91
  */
136
92
  export async function pushFile(
137
- afcService: any,
93
+ client: AfcClient,
138
94
  localPathOrPayload: string | Buffer,
139
95
  remotePath: string,
140
96
  opts: PushFileOptions = {}
141
97
  ): Promise<void> {
142
98
  const {timeoutMs = IO_TIMEOUT_MS} = opts;
143
99
  const timer = new timing.Timer().start();
144
- await remoteMkdirp(afcService, path.dirname(remotePath));
145
- const source = Buffer.isBuffer(localPathOrPayload)
146
- ? localPathOrPayload
147
- : fs.createReadStream(localPathOrPayload, {autoClose: true});
148
- const writeStream = await afcService.createWriteStream(remotePath, {
149
- autoDestroy: true,
150
- });
151
- writeStream.on('finish', writeStream.destroy);
152
- let pushError: Error | null = null;
153
- const filePushPromise = new B<void>((resolve, reject) => {
154
- writeStream.on('close', () => {
155
- if (pushError) {
156
- reject(pushError);
157
- } else {
158
- resolve();
159
- }
160
- });
161
- const onStreamError = (e: Error) => {
162
- if (!Buffer.isBuffer(source)) {
163
- source.unpipe(writeStream);
164
- }
165
- defaultLogger.debug(e);
166
- pushError = e;
167
- };
168
- writeStream.on('error', onStreamError);
169
- if (!Buffer.isBuffer(source)) {
170
- source.on('error', onStreamError);
171
- }
172
- });
173
- if (Buffer.isBuffer(source)) {
174
- writeStream.write(source);
175
- writeStream.end();
176
- } else {
177
- source.pipe(writeStream);
178
- }
179
- await filePushPromise.timeout(Math.max(timeoutMs, 60000));
100
+ await remoteMkdirp(client, path.dirname(remotePath));
101
+
102
+ // AfcClient handles the branching internally
103
+ const pushPromise = Buffer.isBuffer(localPathOrPayload)
104
+ ? client.setFileContents(remotePath, localPathOrPayload)
105
+ : client.writeFromStream(
106
+ remotePath,
107
+ fs.createReadStream(localPathOrPayload, {autoClose: true})
108
+ );
109
+
110
+ // Wrap with timeout
111
+ const actualTimeout = Math.max(timeoutMs, 60000);
112
+ await B.resolve(pushPromise).timeout(
113
+ actualTimeout,
114
+ `Timed out after ${actualTimeout}ms while pushing file to '${remotePath}'`
115
+ );
116
+
180
117
  const fileSize = Buffer.isBuffer(localPathOrPayload)
181
118
  ? localPathOrPayload.length
182
119
  : (await fs.stat(localPathOrPayload)).size;
@@ -189,15 +126,14 @@ export async function pushFile(
189
126
  /**
190
127
  * Pushes a folder to a real device
191
128
  *
192
- * @param afcService Apple File Client service instance from
193
- * 'appium-ios-device' module
129
+ * @param client AFC client instance
194
130
  * @param srcRootPath The full path to the source folder
195
131
  * @param dstRootPath The relative path to the destination folder. The folder
196
132
  * will be deleted if already exists.
197
133
  * @param opts Push folder options
198
134
  */
199
135
  export async function pushFolder(
200
- afcService: any,
136
+ client: AfcClient,
201
137
  srcRootPath: string,
202
138
  dstRootPath: string,
203
139
  opts: PushFolderOptions = {}
@@ -228,16 +164,16 @@ export async function pushFolder(
228
164
  `Got ${util.pluralize('folder', foldersToPush.length, true)} and ` +
229
165
  `${util.pluralize('file', filesToPush.length, true)} to push`,
230
166
  );
231
- // create the folder structure first
167
+ // Create the folder structure
232
168
  try {
233
- await afcService.deleteDirectory(dstRootPath);
169
+ await client.deleteDirectory(dstRootPath);
234
170
  } catch {}
235
- await afcService.createDirectory(dstRootPath);
171
+
172
+ await client.createDirectory(dstRootPath);
236
173
  for (const relativeFolderPath of foldersToPush) {
237
- // createDirectory does not accept folder names ending with a path separator
238
174
  const absoluteFolderPath = _.trimEnd(path.join(dstRootPath, relativeFolderPath), path.sep);
239
175
  if (absoluteFolderPath) {
240
- await afcService.createDirectory(absoluteFolderPath);
176
+ await client.createDirectory(absoluteFolderPath);
241
177
  }
242
178
  }
243
179
  // do not forget about the root folder
@@ -250,29 +186,13 @@ export async function pushFolder(
250
186
  const absoluteSourcePath = path.join(srcRootPath, relativePath);
251
187
  const readStream = fs.createReadStream(absoluteSourcePath, {autoClose: true});
252
188
  const absoluteDestinationPath = path.join(dstRootPath, relativePath);
253
- const writeStream = await afcService.createWriteStream(absoluteDestinationPath, {
254
- autoDestroy: true,
255
- });
256
- writeStream.on('finish', writeStream.destroy);
257
- let pushError: Error | null = null;
258
- const filePushPromise = new B<void>((resolve, reject) => {
259
- writeStream.on('close', () => {
260
- if (pushError) {
261
- reject(pushError);
262
- } else {
263
- resolve();
264
- }
265
- });
266
- const onStreamError = (e: Error) => {
267
- readStream.unpipe(writeStream);
268
- defaultLogger.debug(e);
269
- pushError = e;
270
- };
271
- writeStream.on('error', onStreamError);
272
- readStream.on('error', onStreamError);
273
- });
274
- readStream.pipe(writeStream);
275
- await filePushPromise.timeout(Math.max(timeoutMs - timer.getDuration().asMilliSeconds, 60000));
189
+
190
+ const pushPromise = client.writeFromStream(absoluteDestinationPath, readStream);
191
+ const actualTimeout = Math.max(timeoutMs - timer.getDuration().asMilliSeconds, 60000);
192
+ await B.resolve(pushPromise).timeout(
193
+ actualTimeout,
194
+ `Timed out after ${actualTimeout}ms while pushing '${relativePath}' to '${absoluteDestinationPath}'`
195
+ );
276
196
  };
277
197
 
278
198
  if (enableParallelPush) {
@@ -754,42 +674,28 @@ function isPreferDevicectlEnabled(): boolean {
754
674
  return ['yes', 'true', '1'].includes(_.toLower(process.env.APPIUM_XCUITEST_PREFER_DEVICECTL));
755
675
  };
756
676
 
757
- /**
758
- * Checks a presence of a local folder.
759
- *
760
- * @param folderPath Full path to the local folder
761
- * @returns True if the folder exists and is actually a folder
762
- */
763
- async function folderExists(folderPath: string): Promise<boolean> {
764
- try {
765
- return (await fs.stat(folderPath)).isDirectory();
766
- } catch {
767
- return false;
768
- }
769
- }
770
-
771
677
  /**
772
678
  * Creates remote folder path recursively. Noop if the given path
773
679
  * already exists
774
680
  *
775
- * @param afcService Apple File Client service instance from
776
- * 'appium-ios-device' module
681
+ * @param client AFC client instance
777
682
  * @param remoteRoot The relative path to the remote folder structure
778
683
  * to be created
779
684
  */
780
- async function remoteMkdirp(afcService: any, remoteRoot: string): Promise<void> {
685
+ async function remoteMkdirp(client: AfcClient, remoteRoot: string): Promise<void> {
781
686
  if (remoteRoot === '.' || remoteRoot === '/') {
782
687
  return;
783
688
  }
689
+
784
690
  try {
785
- await afcService.listDirectory(remoteRoot);
691
+ await client.listDirectory(remoteRoot);
786
692
  return;
787
693
  } catch {
788
- // This means that the directory is missing and we got an object not found error.
789
- // Therefore, we are going to the parent
790
- await remoteMkdirp(afcService, path.dirname(remoteRoot));
694
+ // Directory is missing, create parent first
695
+ await remoteMkdirp(client, path.dirname(remoteRoot));
791
696
  }
792
- await afcService.createDirectory(remoteRoot);
697
+
698
+ await client.createDirectory(remoteRoot);
793
699
  }
794
700
 
795
701
  //#endregion
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "appium-xcuitest-driver",
3
- "version": "10.14.13",
3
+ "version": "10.15.0",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "appium-xcuitest-driver",
9
- "version": "10.14.13",
9
+ "version": "10.15.0",
10
10
  "license": "Apache-2.0",
11
11
  "dependencies": {
12
12
  "@appium/strongbox": "^1.0.0-rc.1",
package/package.json CHANGED
@@ -8,7 +8,7 @@
8
8
  "xcuitest",
9
9
  "xctest"
10
10
  ],
11
- "version": "10.14.13",
11
+ "version": "10.15.0",
12
12
  "author": "Appium Contributors",
13
13
  "license": "Apache-2.0",
14
14
  "repository": {