appium-xcuitest-driver 7.14.0 → 7.15.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.
- package/CHANGELOG.md +14 -0
- package/build/lib/app-infos-cache.d.ts +46 -0
- package/build/lib/app-infos-cache.d.ts.map +1 -0
- package/build/lib/app-infos-cache.js +156 -0
- package/build/lib/app-infos-cache.js.map +1 -0
- package/build/lib/app-utils.d.ts +32 -52
- package/build/lib/app-utils.d.ts.map +1 -1
- package/build/lib/app-utils.js +327 -219
- package/build/lib/app-utils.js.map +1 -1
- package/build/lib/commands/app-management.d.ts +5 -4
- package/build/lib/commands/app-management.d.ts.map +1 -1
- package/build/lib/commands/app-management.js +14 -7
- package/build/lib/commands/app-management.js.map +1 -1
- package/build/lib/commands/app-strings.d.ts +5 -2
- package/build/lib/commands/app-strings.d.ts.map +1 -1
- package/build/lib/commands/app-strings.js +6 -3
- package/build/lib/commands/app-strings.js.map +1 -1
- package/build/lib/commands/file-movement.js +1 -1
- package/build/lib/commands/file-movement.js.map +1 -1
- package/build/lib/commands/types.d.ts +1 -0
- package/build/lib/commands/types.d.ts.map +1 -1
- package/build/lib/commands/xctest-record-screen.d.ts +1 -1
- package/build/lib/desired-caps.d.ts +2 -0
- package/build/lib/desired-caps.d.ts.map +1 -1
- package/build/lib/desired-caps.js +1 -0
- package/build/lib/desired-caps.js.map +1 -1
- package/build/lib/driver.d.ts +30 -27
- package/build/lib/driver.d.ts.map +1 -1
- package/build/lib/driver.js +12 -12
- package/build/lib/driver.js.map +1 -1
- package/build/lib/execute-method-map.d.ts +1 -1
- package/build/lib/execute-method-map.js +1 -1
- package/build/lib/execute-method-map.js.map +1 -1
- package/build/lib/ios-fs-helpers.d.ts +30 -15
- package/build/lib/ios-fs-helpers.d.ts.map +1 -1
- package/build/lib/ios-fs-helpers.js +54 -21
- package/build/lib/ios-fs-helpers.js.map +1 -1
- package/build/lib/real-device-management.d.ts +0 -5
- package/build/lib/real-device-management.d.ts.map +1 -1
- package/build/lib/real-device-management.js +8 -5
- package/build/lib/real-device-management.js.map +1 -1
- package/build/lib/real-device.d.ts +13 -9
- package/build/lib/real-device.d.ts.map +1 -1
- package/build/lib/real-device.js +49 -75
- package/build/lib/real-device.js.map +1 -1
- package/lib/app-infos-cache.js +159 -0
- package/lib/app-utils.js +357 -239
- package/lib/commands/app-management.js +20 -9
- package/lib/commands/app-strings.js +6 -3
- package/lib/commands/file-movement.js +1 -1
- package/lib/commands/types.ts +1 -0
- package/lib/desired-caps.js +1 -0
- package/lib/driver.js +12 -15
- package/lib/execute-method-map.ts +1 -1
- package/lib/ios-fs-helpers.js +57 -23
- package/lib/real-device-management.js +7 -5
- package/lib/real-device.js +62 -88
- package/npm-shrinkwrap.json +13 -5
- package/package.json +1 -1
|
@@ -4,7 +4,11 @@ import {errors} from 'appium/driver';
|
|
|
4
4
|
import {services} from 'appium-ios-device';
|
|
5
5
|
import path from 'node:path';
|
|
6
6
|
import B from 'bluebird';
|
|
7
|
-
import {
|
|
7
|
+
import {
|
|
8
|
+
SUPPORTED_EXTENSIONS,
|
|
9
|
+
onPostConfigureApp,
|
|
10
|
+
onDownloadApp,
|
|
11
|
+
} from '../app-utils';
|
|
8
12
|
|
|
9
13
|
export default {
|
|
10
14
|
/**
|
|
@@ -12,15 +16,20 @@ export default {
|
|
|
12
16
|
*
|
|
13
17
|
* Please ensure the app is built for a correct architecture and is signed with a proper developer signature (for real devices) prior to calling this.
|
|
14
18
|
* @param {string} app - See docs for `appium:app` capability
|
|
15
|
-
* @param {
|
|
16
|
-
*
|
|
17
|
-
* @param {boolean} [checkVersion] - If the application installation follows currently installed application's version status if provided.
|
|
19
|
+
* @param {number} [timeoutMs] - The maximum time to wait until app install is finished (in ms) on real devices.
|
|
20
|
+
* If not provided, then the value of `appium:appPushTimeout` capability is used. If the capability is not provided then the default is 240000ms (4 minutes).
|
|
21
|
+
* @param {boolean} [checkVersion] - If the application installation follows currently installed application's version status if provided.
|
|
22
|
+
* No checking occurs if no this option.
|
|
18
23
|
* @privateRemarks Link to capability docs
|
|
19
24
|
* @returns {Promise<void>}
|
|
20
25
|
* @this {XCUITestDriver}
|
|
21
26
|
*/
|
|
22
|
-
async mobileInstallApp(app, timeoutMs,
|
|
23
|
-
const srcAppPath = await this.helpers.configureApp(app,
|
|
27
|
+
async mobileInstallApp(app, timeoutMs, checkVersion) {
|
|
28
|
+
const srcAppPath = await this.helpers.configureApp(app, {
|
|
29
|
+
onPostProcess: onPostConfigureApp.bind(this),
|
|
30
|
+
onDownload: onDownloadApp.bind(this),
|
|
31
|
+
supportedExtensions: SUPPORTED_EXTENSIONS,
|
|
32
|
+
});
|
|
24
33
|
this.log.info(
|
|
25
34
|
`Installing '${srcAppPath}' to the ${this.isRealDevice() ? 'real device' : 'Simulator'} ` +
|
|
26
35
|
`with UDID '${this.device.udid}'`,
|
|
@@ -31,8 +40,8 @@ export default {
|
|
|
31
40
|
);
|
|
32
41
|
}
|
|
33
42
|
|
|
43
|
+
const bundleId = await this.appInfosCache.extractBundleId(srcAppPath);
|
|
34
44
|
if (checkVersion) {
|
|
35
|
-
const bundleId = await extractBundleId(srcAppPath);
|
|
36
45
|
const {install} = await this.checkAutInstallationState({
|
|
37
46
|
enforceAppInstall: false,
|
|
38
47
|
fullReset: false,
|
|
@@ -49,8 +58,10 @@ export default {
|
|
|
49
58
|
|
|
50
59
|
await this.device.installApp(
|
|
51
60
|
srcAppPath,
|
|
52
|
-
|
|
53
|
-
|
|
61
|
+
bundleId,
|
|
62
|
+
{
|
|
63
|
+
timeoutMs: timeoutMs ?? this.opts.appPushTimeout
|
|
64
|
+
},
|
|
54
65
|
);
|
|
55
66
|
this.log.info(`Installation of '${srcAppPath}' succeeded`);
|
|
56
67
|
},
|
|
@@ -4,8 +4,11 @@ export default {
|
|
|
4
4
|
/**
|
|
5
5
|
* Return the language-specific strings for an app
|
|
6
6
|
*
|
|
7
|
-
* @param {string} language - The language abbreviation to fetch app strings mapping for.
|
|
8
|
-
*
|
|
7
|
+
* @param {string} language - The language abbreviation to fetch app strings mapping for.
|
|
8
|
+
* If no language is provided then strings for the 'en language would be returned
|
|
9
|
+
* @param {string|null} [stringFile=null] - Relative path to the corresponding .strings
|
|
10
|
+
* file starting from the corresponding .lproj folder, e.g., `base/main.strings`. If omitted,
|
|
11
|
+
* then Appium will make its best guess where the file is.
|
|
9
12
|
*
|
|
10
13
|
* @returns {Promise<import('@appium/types').StringRecord<string>>} A record of localized keys to localized text
|
|
11
14
|
*
|
|
@@ -13,7 +16,7 @@ export default {
|
|
|
13
16
|
*/
|
|
14
17
|
async getStrings(language, stringFile = null) {
|
|
15
18
|
this.log.debug(`Gettings strings for language '${language}' and string file '${stringFile}'`);
|
|
16
|
-
return await parseLocalizableStrings(
|
|
19
|
+
return await parseLocalizableStrings.bind(this)(
|
|
17
20
|
Object.assign({}, this.opts, {
|
|
18
21
|
language,
|
|
19
22
|
stringFile,
|
|
@@ -160,7 +160,7 @@ async function pushFileToSimulator(device, remotePath, base64Data) {
|
|
|
160
160
|
async function pushFileToRealDevice(device, remotePath, base64Data) {
|
|
161
161
|
const {service, relativePath} = await createService(device.udid, remotePath);
|
|
162
162
|
try {
|
|
163
|
-
await pushFile(service,
|
|
163
|
+
await pushFile(service, Buffer.from(base64Data, 'base64'), relativePath);
|
|
164
164
|
} catch (e) {
|
|
165
165
|
log.debug(e.stack);
|
|
166
166
|
throw new Error(`Could not push the file to '${remotePath}'. Original error: ${e.message}`);
|
package/lib/commands/types.ts
CHANGED
package/lib/desired-caps.js
CHANGED
package/lib/driver.js
CHANGED
|
@@ -14,8 +14,6 @@ import url from 'node:url';
|
|
|
14
14
|
import {
|
|
15
15
|
SUPPORTED_EXTENSIONS,
|
|
16
16
|
SAFARI_BUNDLE_ID,
|
|
17
|
-
extractBundleId,
|
|
18
|
-
extractBundleVersion,
|
|
19
17
|
onPostConfigureApp,
|
|
20
18
|
onDownloadApp,
|
|
21
19
|
verifyApplicationPlatform,
|
|
@@ -55,7 +53,6 @@ import {
|
|
|
55
53
|
getAndCheckXcodeVersion,
|
|
56
54
|
getDriverInfo,
|
|
57
55
|
isLocalHost,
|
|
58
|
-
isTvOs,
|
|
59
56
|
markSystemFilesForCleanup,
|
|
60
57
|
normalizeCommandTimeouts,
|
|
61
58
|
normalizePlatformVersion,
|
|
@@ -63,6 +60,7 @@ import {
|
|
|
63
60
|
removeAllSessionWebSocketHandlers,
|
|
64
61
|
translateDeviceName,
|
|
65
62
|
} from './utils';
|
|
63
|
+
import { AppInfosCache } from './app-infos-cache';
|
|
66
64
|
|
|
67
65
|
const SHUTDOWN_OTHER_FEAT_NAME = 'shutdown_other_sims';
|
|
68
66
|
const CUSTOMIZE_RESULT_BUNDLE_PATH = 'customize_result_bundle_path';
|
|
@@ -310,6 +308,7 @@ export class XCUITestDriver extends BaseDriver {
|
|
|
310
308
|
}
|
|
311
309
|
this.lifecycleData = {};
|
|
312
310
|
this._audioRecorder = null;
|
|
311
|
+
this.appInfosCache = new AppInfosCache(this.log);
|
|
313
312
|
}
|
|
314
313
|
|
|
315
314
|
async onSettingsUpdate(key, value) {
|
|
@@ -548,7 +547,7 @@ export class XCUITestDriver extends BaseDriver {
|
|
|
548
547
|
await checkAppPresent(this.opts.app);
|
|
549
548
|
|
|
550
549
|
if (!this.opts.bundleId) {
|
|
551
|
-
this.opts.bundleId = await extractBundleId(this.opts.app);
|
|
550
|
+
this.opts.bundleId = await this.appInfosCache.extractBundleId(this.opts.app);
|
|
552
551
|
}
|
|
553
552
|
}
|
|
554
553
|
|
|
@@ -1503,7 +1502,7 @@ export class XCUITestDriver extends BaseDriver {
|
|
|
1503
1502
|
};
|
|
1504
1503
|
}
|
|
1505
1504
|
|
|
1506
|
-
const candidateBundleVersion = await extractBundleVersion(app);
|
|
1505
|
+
const candidateBundleVersion = await this.appInfosCache.extractBundleVersion(app);
|
|
1507
1506
|
this.log.debug(`CFBundleVersion from Info.plist: ${candidateBundleVersion}`);
|
|
1508
1507
|
if (!candidateBundleVersion) {
|
|
1509
1508
|
return {
|
|
@@ -1560,10 +1559,7 @@ export class XCUITestDriver extends BaseDriver {
|
|
|
1560
1559
|
return;
|
|
1561
1560
|
}
|
|
1562
1561
|
|
|
1563
|
-
await verifyApplicationPlatform(this
|
|
1564
|
-
isSimulator: this.isSimulator(),
|
|
1565
|
-
isTvOS: isTvOs(this.opts.platformName),
|
|
1566
|
-
});
|
|
1562
|
+
await verifyApplicationPlatform.bind(this)();
|
|
1567
1563
|
|
|
1568
1564
|
const {install, skipUninstall} = await this.checkAutInstallationState();
|
|
1569
1565
|
if (install) {
|
|
@@ -1571,7 +1567,6 @@ export class XCUITestDriver extends BaseDriver {
|
|
|
1571
1567
|
await installToRealDevice.bind(this)(this.opts.app, this.opts.bundleId, {
|
|
1572
1568
|
skipUninstall,
|
|
1573
1569
|
timeout: this.opts.appPushTimeout,
|
|
1574
|
-
strategy: this.opts.appInstallStrategy,
|
|
1575
1570
|
});
|
|
1576
1571
|
} else {
|
|
1577
1572
|
await installToSimulator.bind(this)(this.opts.app, this.opts.bundleId, {
|
|
@@ -1607,9 +1602,13 @@ export class XCUITestDriver extends BaseDriver {
|
|
|
1607
1602
|
}
|
|
1608
1603
|
|
|
1609
1604
|
/** @type {string[]} */
|
|
1610
|
-
const appPaths = await B.all(appsList.map((app) => this.helpers.configureApp(app,
|
|
1605
|
+
const appPaths = await B.all(appsList.map((app) => this.helpers.configureApp(app, {
|
|
1606
|
+
onPostProcess: onPostConfigureApp.bind(this),
|
|
1607
|
+
onDownload: onDownloadApp.bind(this),
|
|
1608
|
+
supportedExtensions: SUPPORTED_EXTENSIONS,
|
|
1609
|
+
})));
|
|
1611
1610
|
/** @type {string[]} */
|
|
1612
|
-
const appIds = await B.all(appPaths.map((appPath) => extractBundleId(appPath)));
|
|
1611
|
+
const appIds = await B.all(appPaths.map((appPath) => this.appInfosCache.extractBundleId(appPath)));
|
|
1613
1612
|
for (const [appId, appPath] of _.zip(appIds, appPaths)) {
|
|
1614
1613
|
if (this.isRealDevice()) {
|
|
1615
1614
|
await installToRealDevice.bind(this)(
|
|
@@ -1618,7 +1617,6 @@ export class XCUITestDriver extends BaseDriver {
|
|
|
1618
1617
|
{
|
|
1619
1618
|
skipUninstall: true, // to make the behavior as same as UIA2
|
|
1620
1619
|
timeout: this.opts.appPushTimeout,
|
|
1621
|
-
strategy: this.opts.appInstallStrategy,
|
|
1622
1620
|
},
|
|
1623
1621
|
);
|
|
1624
1622
|
} else {
|
|
@@ -1689,7 +1687,7 @@ export class XCUITestDriver extends BaseDriver {
|
|
|
1689
1687
|
return;
|
|
1690
1688
|
}
|
|
1691
1689
|
|
|
1692
|
-
const candidateBundleId = await extractBundleId(this.opts.prebuiltWDAPath);
|
|
1690
|
+
const candidateBundleId = await this.appInfosCache.extractBundleId(this.opts.prebuiltWDAPath);
|
|
1693
1691
|
this.wda.updatedWDABundleId = candidateBundleId.replace('.xctrunner', '');
|
|
1694
1692
|
this.log.info(
|
|
1695
1693
|
`Installing prebuilt WDA at '${this.opts.prebuiltWDAPath}'. ` +
|
|
@@ -1705,7 +1703,6 @@ export class XCUITestDriver extends BaseDriver {
|
|
|
1705
1703
|
{
|
|
1706
1704
|
skipUninstall: true,
|
|
1707
1705
|
timeout: this.opts.appPushTimeout,
|
|
1708
|
-
strategy: this.opts.appInstallStrategy,
|
|
1709
1706
|
},
|
|
1710
1707
|
);
|
|
1711
1708
|
} else {
|
package/lib/ios-fs-helpers.js
CHANGED
|
@@ -4,7 +4,7 @@ import {fs, tempDir, mkdirp, zip, util, timing} from 'appium/support';
|
|
|
4
4
|
import path from 'path';
|
|
5
5
|
import log from './logger';
|
|
6
6
|
|
|
7
|
-
const IO_TIMEOUT_MS = 4 * 60 * 1000;
|
|
7
|
+
export const IO_TIMEOUT_MS = 4 * 60 * 1000;
|
|
8
8
|
// Mobile devices use NAND memory modules for the storage,
|
|
9
9
|
// and the parallelism there is not as performant as on regular SSDs
|
|
10
10
|
const MAX_IO_CHUNK_SIZE = 8;
|
|
@@ -17,7 +17,7 @@ const MAX_IO_CHUNK_SIZE = 8;
|
|
|
17
17
|
* @param {string} remotePath Relative path to the file on the device
|
|
18
18
|
* @returns {Promise<Buffer>} The file content as a buffer
|
|
19
19
|
*/
|
|
20
|
-
async function pullFile(afcService, remotePath) {
|
|
20
|
+
export async function pullFile(afcService, remotePath) {
|
|
21
21
|
const stream = await afcService.createReadStream(remotePath, {autoDestroy: true});
|
|
22
22
|
const pullPromise = new B((resolve, reject) => {
|
|
23
23
|
stream.on('close', resolve);
|
|
@@ -51,7 +51,7 @@ async function folderExists(folderPath) {
|
|
|
51
51
|
* @param {string} remoteRootPath Relative path to the folder on the device
|
|
52
52
|
* @returns {Promise<Buffer>} The folder content as a zipped base64-encoded buffer
|
|
53
53
|
*/
|
|
54
|
-
async function pullFolder(afcService, remoteRootPath) {
|
|
54
|
+
export async function pullFolder(afcService, remoteRootPath) {
|
|
55
55
|
const tmpFolder = await tempDir.openDir();
|
|
56
56
|
try {
|
|
57
57
|
let localTopItem = null;
|
|
@@ -145,35 +145,71 @@ async function remoteMkdirp(afcService, remoteRoot) {
|
|
|
145
145
|
await afcService.createDirectory(remoteRoot);
|
|
146
146
|
}
|
|
147
147
|
|
|
148
|
+
/**
|
|
149
|
+
* @typedef {Object} PushFileOptions
|
|
150
|
+
* @property {number} [timeoutMs=240000] The maximum count of milliceconds to wait until
|
|
151
|
+
* file push is completed. Cannot be lower than 60000ms
|
|
152
|
+
*/
|
|
153
|
+
|
|
148
154
|
/**
|
|
149
155
|
* Pushes a file to a real device
|
|
150
156
|
*
|
|
151
|
-
* @param {any} afcService Apple File Client service instance from
|
|
157
|
+
* @param {any} afcService afcService Apple File Client service instance from
|
|
152
158
|
* 'appium-ios-device' module
|
|
159
|
+
* @param {string|Buffer} localPathOrPayload Either full path to the source file
|
|
160
|
+
* or a buffer payload to be written into the remote destination
|
|
153
161
|
* @param {string} remotePath Relative path to the file on the device. The remote
|
|
154
162
|
* folder structure is created automatically if necessary.
|
|
155
|
-
* @param {
|
|
163
|
+
* @param {PushFileOptions} [opts={}]
|
|
156
164
|
*/
|
|
157
|
-
async function pushFile(afcService, remotePath,
|
|
165
|
+
export async function pushFile (afcService, localPathOrPayload, remotePath, opts = {}) {
|
|
166
|
+
const {
|
|
167
|
+
timeoutMs = IO_TIMEOUT_MS,
|
|
168
|
+
} = opts;
|
|
169
|
+
const timer = new timing.Timer().start();
|
|
158
170
|
await remoteMkdirp(afcService, path.dirname(remotePath));
|
|
159
|
-
const
|
|
171
|
+
const source = Buffer.isBuffer(localPathOrPayload)
|
|
172
|
+
? localPathOrPayload
|
|
173
|
+
: fs.createReadStream(localPathOrPayload, {autoClose: true});
|
|
174
|
+
const writeStream = await afcService.createWriteStream(remotePath, {
|
|
175
|
+
autoDestroy: true,
|
|
176
|
+
});
|
|
177
|
+
writeStream.on('finish', writeStream.destroy);
|
|
160
178
|
let pushError = null;
|
|
161
|
-
const
|
|
162
|
-
|
|
163
|
-
pushError = e;
|
|
164
|
-
});
|
|
165
|
-
stream.on('close', () => {
|
|
179
|
+
const filePushPromise = new B((resolve, reject) => {
|
|
180
|
+
writeStream.on('close', () => {
|
|
166
181
|
if (pushError) {
|
|
167
182
|
reject(pushError);
|
|
168
183
|
} else {
|
|
169
184
|
resolve();
|
|
170
185
|
}
|
|
171
186
|
});
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
187
|
+
const onStreamError = (e) => {
|
|
188
|
+
if (!Buffer.isBuffer(source)) {
|
|
189
|
+
source.unpipe(writeStream);
|
|
190
|
+
}
|
|
191
|
+
log.debug(e);
|
|
192
|
+
pushError = e;
|
|
193
|
+
};
|
|
194
|
+
writeStream.on('error', onStreamError);
|
|
195
|
+
if (!Buffer.isBuffer(source)) {
|
|
196
|
+
source.on('error', onStreamError);
|
|
197
|
+
}
|
|
198
|
+
});
|
|
199
|
+
if (Buffer.isBuffer(source)) {
|
|
200
|
+
writeStream.write(source);
|
|
201
|
+
} else {
|
|
202
|
+
source.pipe(writeStream);
|
|
203
|
+
}
|
|
204
|
+
await filePushPromise.timeout(Math.max(timeoutMs, 60000));
|
|
205
|
+
const fileSize = Buffer.isBuffer(localPathOrPayload)
|
|
206
|
+
? localPathOrPayload.length
|
|
207
|
+
: (await fs.stat(localPathOrPayload)).size;
|
|
208
|
+
log.debug(
|
|
209
|
+
`Successfully pushed the file payload (${util.toReadableSizeString(fileSize)}) ` +
|
|
210
|
+
`to the remote location '${remotePath}' in ${timer.getDuration().asMilliSeconds.toFixed(0)}ms`
|
|
211
|
+
);
|
|
212
|
+
};
|
|
177
213
|
|
|
178
214
|
/**
|
|
179
215
|
* @typedef {Object} PushFolderOptions
|
|
@@ -194,7 +230,7 @@ async function pushFile(afcService, remotePath, base64Data) {
|
|
|
194
230
|
* will be deleted if already exists.
|
|
195
231
|
* @param {PushFolderOptions} opts
|
|
196
232
|
*/
|
|
197
|
-
async function pushFolder(afcService, srcRootPath, dstRootPath, opts = {}) {
|
|
233
|
+
export async function pushFolder(afcService, srcRootPath, dstRootPath, opts = {}) {
|
|
198
234
|
const {timeoutMs = IO_TIMEOUT_MS, enableParallelPush = false} = opts;
|
|
199
235
|
|
|
200
236
|
const timer = new timing.Timer().start();
|
|
@@ -239,7 +275,7 @@ async function pushFolder(afcService, srcRootPath, dstRootPath, opts = {}) {
|
|
|
239
275
|
`(${util.pluralize('item', foldersToPush.length + 1, true)})`,
|
|
240
276
|
);
|
|
241
277
|
|
|
242
|
-
const
|
|
278
|
+
const _pushFile = async (/** @type {string} */ relativePath) => {
|
|
243
279
|
const absoluteSourcePath = path.join(srcRootPath, relativePath);
|
|
244
280
|
const readStream = fs.createReadStream(absoluteSourcePath, {autoClose: true});
|
|
245
281
|
const absoluteDestinationPath = path.join(dstRootPath, relativePath);
|
|
@@ -272,7 +308,7 @@ async function pushFolder(afcService, srcRootPath, dstRootPath, opts = {}) {
|
|
|
272
308
|
log.debug(`Proceeding to parallel files push (max ${MAX_IO_CHUNK_SIZE} writers)`);
|
|
273
309
|
const pushPromises = [];
|
|
274
310
|
for (const relativeFilePath of filesToPush) {
|
|
275
|
-
pushPromises.push(B.resolve(
|
|
311
|
+
pushPromises.push(B.resolve(_pushFile(relativeFilePath)));
|
|
276
312
|
// keep the push queue filled
|
|
277
313
|
if (pushPromises.length >= MAX_IO_CHUNK_SIZE) {
|
|
278
314
|
await B.any(pushPromises);
|
|
@@ -290,7 +326,7 @@ async function pushFolder(afcService, srcRootPath, dstRootPath, opts = {}) {
|
|
|
290
326
|
} else {
|
|
291
327
|
log.debug(`Proceeding to serial files push`);
|
|
292
328
|
for (const relativeFilePath of filesToPush) {
|
|
293
|
-
await
|
|
329
|
+
await _pushFile(relativeFilePath);
|
|
294
330
|
const elapsedMs = timer.getDuration().asMilliSeconds;
|
|
295
331
|
if (elapsedMs > timeoutMs) {
|
|
296
332
|
throw new B.TimeoutError(`Timed out after ${elapsedMs} ms`);
|
|
@@ -304,5 +340,3 @@ async function pushFolder(afcService, srcRootPath, dstRootPath, opts = {}) {
|
|
|
304
340
|
`within ${timer.getDuration().asMilliSeconds.toFixed(0)}ms`,
|
|
305
341
|
);
|
|
306
342
|
}
|
|
307
|
-
|
|
308
|
-
export {pullFile, pullFolder, pushFile, pushFolder};
|
|
@@ -8,7 +8,6 @@ const DEFAULT_APP_INSTALLATION_TIMEOUT_MS = 8 * 60 * 1000;
|
|
|
8
8
|
* @typedef {Object} InstallOptions
|
|
9
9
|
*
|
|
10
10
|
* @property {boolean} [skipUninstall] Whether to skip app uninstall before installing it
|
|
11
|
-
* @property {'serial'|'parallel'|'ios-deploy'} [strategy='serial'] One of possible install strategies ('serial', 'parallel', 'ios-deploy')
|
|
12
11
|
* @property {number} [timeout=480000] App install timeout
|
|
13
12
|
* @property {boolean} [shouldEnforceUninstall] Whether to enforce the app uninstallation. e.g. fullReset, or enforceAppInstall is true
|
|
14
13
|
*/
|
|
@@ -29,7 +28,6 @@ export async function installToRealDevice(app, bundleId, opts = {}) {
|
|
|
29
28
|
|
|
30
29
|
const {
|
|
31
30
|
skipUninstall,
|
|
32
|
-
strategy,
|
|
33
31
|
timeout = DEFAULT_APP_INSTALLATION_TIMEOUT_MS,
|
|
34
32
|
} = opts;
|
|
35
33
|
|
|
@@ -37,10 +35,12 @@ export async function installToRealDevice(app, bundleId, opts = {}) {
|
|
|
37
35
|
this.log.info(`Reset requested. Removing app with id '${bundleId}' from the device`);
|
|
38
36
|
await device.remove(bundleId);
|
|
39
37
|
}
|
|
40
|
-
this.log.debug(`Installing '${app}' on the device with UUID '${device.udid}'
|
|
38
|
+
this.log.debug(`Installing '${app}' on the device with UUID '${device.udid}'`);
|
|
41
39
|
|
|
42
40
|
try {
|
|
43
|
-
await device.install(app,
|
|
41
|
+
await device.install(app, bundleId, {
|
|
42
|
+
timeoutMs: timeout,
|
|
43
|
+
});
|
|
44
44
|
this.log.debug('The app has been installed successfully.');
|
|
45
45
|
} catch (e) {
|
|
46
46
|
// Want to clarify the device's application installation state in this situation.
|
|
@@ -61,7 +61,9 @@ export async function installToRealDevice(app, bundleId, opts = {}) {
|
|
|
61
61
|
`be already cached on the device, probably with a different signature. ` +
|
|
62
62
|
`Will try to remove it and install a new copy. Original error: ${e.message}`);
|
|
63
63
|
await device.remove(bundleId);
|
|
64
|
-
await device.install(app,
|
|
64
|
+
await device.install(app, bundleId, {
|
|
65
|
+
timeoutMs: timeout,
|
|
66
|
+
});
|
|
65
67
|
this.log.debug('The app has been installed after one retrial.');
|
|
66
68
|
}
|
|
67
69
|
}
|
package/lib/real-device.js
CHANGED
|
@@ -1,23 +1,16 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {timing, util, fs} from 'appium/support';
|
|
2
2
|
import path from 'path';
|
|
3
3
|
import {services, utilities, INSTRUMENT_CHANNEL} from 'appium-ios-device';
|
|
4
4
|
import B from 'bluebird';
|
|
5
5
|
import defaultLogger from './logger';
|
|
6
6
|
import _ from 'lodash';
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {pushFolder} from './ios-fs-helpers';
|
|
7
|
+
import {SAFARI_BUNDLE_ID} from './app-utils';
|
|
8
|
+
import {pushFile, pushFolder, IO_TIMEOUT_MS} from './ios-fs-helpers';
|
|
10
9
|
import { Devicectl } from './devicectl';
|
|
11
10
|
|
|
12
11
|
const APPLICATION_INSTALLED_NOTIFICATION = 'com.apple.mobile.application_installed';
|
|
13
|
-
const INSTALLATION_STAGING_DIR = 'PublicStaging';
|
|
14
12
|
const APPLICATION_NOTIFICATION_TIMEOUT_MS = 30 * 1000;
|
|
15
|
-
const
|
|
16
|
-
const APP_INSTALL_STRATEGY = Object.freeze({
|
|
17
|
-
SERIAL: 'serial',
|
|
18
|
-
PARALLEL: 'parallel',
|
|
19
|
-
IOS_DEPLOY,
|
|
20
|
-
});
|
|
13
|
+
const INSTALLATION_STAGING_DIR = 'PublicStaging';
|
|
21
14
|
|
|
22
15
|
/**
|
|
23
16
|
* @returns {Promise<string[]>}
|
|
@@ -26,6 +19,11 @@ export async function getConnectedDevices() {
|
|
|
26
19
|
return await utilities.getConnectedDevices();
|
|
27
20
|
}
|
|
28
21
|
|
|
22
|
+
/**
|
|
23
|
+
* @typedef {Object} InstallOptions
|
|
24
|
+
* @param {number} [timeoutMs=240000] Application installation timeout in milliseconds
|
|
25
|
+
*/
|
|
26
|
+
|
|
29
27
|
/**
|
|
30
28
|
* @typedef {Object} InstallOrUpgradeOptions
|
|
31
29
|
* @property {number} timeout Install/upgrade timeout in milliseconds
|
|
@@ -71,82 +69,52 @@ export class RealDevice {
|
|
|
71
69
|
|
|
72
70
|
/**
|
|
73
71
|
*
|
|
74
|
-
* @param {string}
|
|
75
|
-
* @param {
|
|
76
|
-
* @param {
|
|
77
|
-
* @privateRemarks This really needs type guards built out
|
|
72
|
+
* @param {string} appPath
|
|
73
|
+
* @param {string} bundleId
|
|
74
|
+
* @param {InstallOptions} [opts={}]
|
|
78
75
|
*/
|
|
79
|
-
async install(
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
) {
|
|
84
|
-
throw new Error(
|
|
85
|
-
`App installation strategy '${strategy}' is unknown. ` +
|
|
86
|
-
`Only the following strategies are supported: ${_.values(APP_INSTALL_STRATEGY)}`,
|
|
87
|
-
);
|
|
88
|
-
}
|
|
89
|
-
this.log.debug(
|
|
90
|
-
`Using '${strategy ?? APP_INSTALL_STRATEGY.SERIAL}' app deployment strategy. ` +
|
|
91
|
-
`You could change it by providing another value to the 'appInstallStrategy' capability`,
|
|
92
|
-
);
|
|
93
|
-
|
|
94
|
-
const installWithIosDeploy = async () => {
|
|
95
|
-
try {
|
|
96
|
-
await fs.which(IOS_DEPLOY);
|
|
97
|
-
} catch (err) {
|
|
98
|
-
throw new Error(`'${IOS_DEPLOY}' utility has not been found in PATH. Is it installed?`);
|
|
99
|
-
}
|
|
100
|
-
try {
|
|
101
|
-
await exec(IOS_DEPLOY, ['--id', this.udid, '--bundle', app], {timeout});
|
|
102
|
-
} catch (err) {
|
|
103
|
-
throw new Error(err.stderr || err.stdout || err.message);
|
|
104
|
-
}
|
|
105
|
-
};
|
|
106
|
-
|
|
76
|
+
async install(appPath, bundleId, opts = {}) {
|
|
77
|
+
const {
|
|
78
|
+
timeoutMs = IO_TIMEOUT_MS,
|
|
79
|
+
} = opts;
|
|
107
80
|
const timer = new timing.Timer().start();
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
await pushFolder(afcService, app, bundlePathOnPhone, {
|
|
117
|
-
enableParallelPush,
|
|
118
|
-
timeoutMs: timeout,
|
|
81
|
+
const afcService = await services.startAfcService(this.udid);
|
|
82
|
+
try {
|
|
83
|
+
let bundlePathOnPhone;
|
|
84
|
+
if ((await fs.stat(appPath)).isFile()) {
|
|
85
|
+
// https://github.com/doronz88/pymobiledevice3/blob/6ff5001f5776e03b610363254e82d7fbcad4ef5f/pymobiledevice3/services/installation_proxy.py#L75
|
|
86
|
+
bundlePathOnPhone = `/${path.basename(appPath)}`;
|
|
87
|
+
await pushFile(afcService, appPath, bundlePathOnPhone, {
|
|
88
|
+
timeoutMs,
|
|
119
89
|
});
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
);
|
|
133
|
-
if (!enableParallelPush) {
|
|
134
|
-
this.log.info(`Consider setting the value of 'appInstallStrategy' capability to 'parallel'`);
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
this.log.warn(`Falling back to '${IOS_DEPLOY}' usage`);
|
|
138
|
-
try {
|
|
139
|
-
await installWithIosDeploy();
|
|
140
|
-
} catch (err1) {
|
|
141
|
-
throw new Error(
|
|
142
|
-
`Could not install '${app}':\n` + ` - ${err.message}\n` + ` - ${err1.message}`,
|
|
143
|
-
);
|
|
90
|
+
} else {
|
|
91
|
+
bundlePathOnPhone = `${INSTALLATION_STAGING_DIR}/${bundleId}`;
|
|
92
|
+
await pushFolder(afcService, appPath, bundlePathOnPhone, {
|
|
93
|
+
enableParallelPush: true,
|
|
94
|
+
timeoutMs,
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
await this.installOrUpgradeApplication(
|
|
98
|
+
bundlePathOnPhone,
|
|
99
|
+
{
|
|
100
|
+
timeout: Math.max(timeoutMs - timer.getDuration().asMilliSeconds, 60000),
|
|
101
|
+
isUpgrade: await this.isAppInstalled(bundleId),
|
|
144
102
|
}
|
|
145
|
-
|
|
146
|
-
|
|
103
|
+
);
|
|
104
|
+
} catch (err) {
|
|
105
|
+
this.log.debug(err.stack);
|
|
106
|
+
let errMessage = `Cannot install the ${bundleId} application`;
|
|
107
|
+
if (err instanceof B.TimeoutError) {
|
|
108
|
+
errMessage += `. Consider increasing the value of 'appPushTimeout' capability (the current value equals to ${timeoutMs}ms)`;
|
|
147
109
|
}
|
|
110
|
+
errMessage += `. Original error: ${err.message}`;
|
|
111
|
+
throw new Error(errMessage);
|
|
112
|
+
} finally {
|
|
113
|
+
afcService.close();
|
|
148
114
|
}
|
|
149
|
-
this.log.info(
|
|
115
|
+
this.log.info(
|
|
116
|
+
`The installation of '${bundleId}' succeeded after ${timer.getDuration().asMilliSeconds.toFixed(0)}ms`
|
|
117
|
+
);
|
|
150
118
|
}
|
|
151
119
|
|
|
152
120
|
/**
|
|
@@ -164,10 +132,16 @@ export class RealDevice {
|
|
|
164
132
|
const clientOptions = {PackageType: 'Developer'};
|
|
165
133
|
try {
|
|
166
134
|
if (isUpgrade) {
|
|
167
|
-
this.log.debug(
|
|
135
|
+
this.log.debug(
|
|
136
|
+
`An upgrade of the existing application is going to be performed. ` +
|
|
137
|
+
`Will timeout in ${timeout.toFixed(0)} ms`
|
|
138
|
+
);
|
|
168
139
|
await installationService.upgradeApplication(bundlePathOnPhone, clientOptions, timeout);
|
|
169
140
|
} else {
|
|
170
|
-
this.log.debug(
|
|
141
|
+
this.log.debug(
|
|
142
|
+
`A new application installation is going to be performed. ` +
|
|
143
|
+
`Will timeout in ${timeout.toFixed(0)} ms`
|
|
144
|
+
);
|
|
171
145
|
await installationService.installApplication(bundlePathOnPhone, clientOptions, timeout);
|
|
172
146
|
}
|
|
173
147
|
try {
|
|
@@ -187,12 +161,12 @@ export class RealDevice {
|
|
|
187
161
|
|
|
188
162
|
/**
|
|
189
163
|
* Alias for {@linkcode install}
|
|
190
|
-
* @param {string}
|
|
191
|
-
* @param {
|
|
192
|
-
* @param {
|
|
164
|
+
* @param {string} appPath
|
|
165
|
+
* @param {string} bundleId
|
|
166
|
+
* @param {InstallOptions} [opts={}]
|
|
193
167
|
*/
|
|
194
|
-
async installApp(
|
|
195
|
-
return await this.install(
|
|
168
|
+
async installApp(appPath, bundleId, opts = {}) {
|
|
169
|
+
return await this.install(appPath, bundleId, opts);
|
|
196
170
|
}
|
|
197
171
|
|
|
198
172
|
/**
|