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.
- package/CHANGELOG.md +6 -0
- package/build/lib/commands/app-management.js +1 -1
- package/build/lib/commands/app-management.js.map +1 -1
- package/build/lib/commands/file-movement.d.ts.map +1 -1
- package/build/lib/commands/file-movement.js +4 -4
- package/build/lib/commands/file-movement.js.map +1 -1
- package/build/lib/commands/memory.js +1 -1
- package/build/lib/commands/memory.js.map +1 -1
- package/build/lib/commands/performance.d.ts.map +1 -1
- package/build/lib/commands/performance.js +13 -4
- package/build/lib/commands/performance.js.map +1 -1
- package/build/lib/commands/xctest-record-screen.js +1 -1
- package/build/lib/commands/xctest-record-screen.js.map +1 -1
- package/build/lib/device/device-connections-factory.d.ts +18 -0
- package/build/lib/device/device-connections-factory.d.ts.map +1 -0
- package/build/lib/{device-connections-factory.js → device/device-connections-factory.js} +57 -41
- package/build/lib/device/device-connections-factory.js.map +1 -0
- package/build/lib/device/real-device-management.d.ts +146 -0
- package/build/lib/device/real-device-management.d.ts.map +1 -0
- package/build/lib/device/real-device-management.js +727 -0
- package/build/lib/device/real-device-management.js.map +1 -0
- package/build/lib/device/simulator-management.d.ts +65 -0
- package/build/lib/device/simulator-management.d.ts.map +1 -0
- package/build/lib/{simulator-management.js → device/simulator-management.js} +23 -42
- package/build/lib/device/simulator-management.js.map +1 -0
- package/build/lib/driver.d.ts +1 -1
- package/build/lib/driver.d.ts.map +1 -1
- package/build/lib/driver.js +5 -6
- package/build/lib/driver.js.map +1 -1
- package/lib/commands/app-management.js +1 -1
- package/lib/commands/file-movement.js +8 -4
- package/lib/commands/memory.js +1 -1
- package/lib/commands/performance.js +12 -1
- package/lib/commands/xctest-record-screen.js +1 -1
- package/lib/{device-connections-factory.js → device/device-connections-factory.ts} +96 -60
- package/lib/device/real-device-management.ts +818 -0
- package/lib/{simulator-management.js → device/simulator-management.ts} +68 -61
- package/lib/driver.js +3 -5
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
- package/build/lib/device-connections-factory.d.ts +0 -13
- package/build/lib/device-connections-factory.d.ts.map +0 -1
- package/build/lib/device-connections-factory.js.map +0 -1
- package/build/lib/ios-fs-helpers.d.ts +0 -75
- package/build/lib/ios-fs-helpers.d.ts.map +0 -1
- package/build/lib/ios-fs-helpers.js +0 -370
- package/build/lib/ios-fs-helpers.js.map +0 -1
- package/build/lib/real-device-management.d.ts +0 -53
- package/build/lib/real-device-management.d.ts.map +0 -1
- package/build/lib/real-device-management.js +0 -128
- package/build/lib/real-device-management.js.map +0 -1
- package/build/lib/real-device.d.ts +0 -112
- package/build/lib/real-device.d.ts.map +0 -1
- package/build/lib/real-device.js +0 -352
- package/build/lib/real-device.js.map +0 -1
- package/build/lib/simulator-management.d.ts +0 -96
- package/build/lib/simulator-management.d.ts.map +0 -1
- package/build/lib/simulator-management.js.map +0 -1
- package/build/lib/xcrun.d.ts +0 -3
- package/build/lib/xcrun.d.ts.map +0 -1
- package/build/lib/xcrun.js +0 -17
- package/build/lib/xcrun.js.map +0 -1
- package/lib/ios-fs-helpers.js +0 -355
- package/lib/real-device-management.js +0 -133
- package/lib/real-device.js +0 -347
- package/lib/xcrun.js +0 -16
package/lib/real-device.js
DELETED
|
@@ -1,347 +0,0 @@
|
|
|
1
|
-
import {timing, util, fs} from 'appium/support';
|
|
2
|
-
import path from 'path';
|
|
3
|
-
import {services, utilities, INSTRUMENT_CHANNEL} from 'appium-ios-device';
|
|
4
|
-
import B, {TimeoutError} from 'bluebird';
|
|
5
|
-
import defaultLogger from './logger';
|
|
6
|
-
import _ from 'lodash';
|
|
7
|
-
import {SAFARI_BUNDLE_ID} from './app-utils';
|
|
8
|
-
import {pushFile, pushFolder, IO_TIMEOUT_MS} from './ios-fs-helpers';
|
|
9
|
-
import { Devicectl } from 'node-devicectl';
|
|
10
|
-
|
|
11
|
-
const APPLICATION_INSTALLED_NOTIFICATION = 'com.apple.mobile.application_installed';
|
|
12
|
-
const APPLICATION_NOTIFICATION_TIMEOUT_MS = 30 * 1000;
|
|
13
|
-
const INSTALLATION_STAGING_DIR = 'PublicStaging';
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* @returns {Promise<string[]>}
|
|
17
|
-
*/
|
|
18
|
-
export async function getConnectedDevices() {
|
|
19
|
-
if (['yes', 'true', '1'].includes(_.toLower(process.env.APPIUM_XCUITEST_PREFER_DEVICECTL))) {
|
|
20
|
-
return (await new Devicectl('').listDevices())
|
|
21
|
-
.map(({hardwareProperties}) => hardwareProperties?.udid)
|
|
22
|
-
.filter(Boolean);
|
|
23
|
-
}
|
|
24
|
-
return await utilities.getConnectedDevices();
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* @typedef {Object} InstallOptions
|
|
29
|
-
* @param {number} [timeoutMs=240000] Application installation timeout in milliseconds
|
|
30
|
-
*/
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* @typedef {Object} InstallOrUpgradeOptions
|
|
34
|
-
* @property {number} timeout Install/upgrade timeout in milliseconds
|
|
35
|
-
* @property {boolean} isUpgrade Whether it is an app upgrade or a new install
|
|
36
|
-
*/
|
|
37
|
-
|
|
38
|
-
export class RealDevice {
|
|
39
|
-
/**
|
|
40
|
-
* @param {string} udid
|
|
41
|
-
* @param {import('@appium/types').AppiumLogger} [logger]
|
|
42
|
-
*/
|
|
43
|
-
constructor(udid, logger) {
|
|
44
|
-
this.udid = udid;
|
|
45
|
-
this._log = logger ?? defaultLogger;
|
|
46
|
-
this.devicectl = new Devicectl(this.udid);
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
/**
|
|
50
|
-
* @returns {import('@appium/types').AppiumLogger}
|
|
51
|
-
*/
|
|
52
|
-
get log() {
|
|
53
|
-
return this._log;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
/**
|
|
57
|
-
* @param {string} bundleId
|
|
58
|
-
*/
|
|
59
|
-
async remove(bundleId) {
|
|
60
|
-
const service = await services.startInstallationProxyService(this.udid);
|
|
61
|
-
try {
|
|
62
|
-
await service.uninstallApplication(bundleId);
|
|
63
|
-
} finally {
|
|
64
|
-
service.close();
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
/**
|
|
69
|
-
* @param {string} bundleId
|
|
70
|
-
*/
|
|
71
|
-
async removeApp(bundleId) {
|
|
72
|
-
await this.remove(bundleId);
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
/**
|
|
76
|
-
*
|
|
77
|
-
* @param {string} appPath
|
|
78
|
-
* @param {string} bundleId
|
|
79
|
-
* @param {InstallOptions} [opts={}]
|
|
80
|
-
*/
|
|
81
|
-
async install(appPath, bundleId, opts = {}) {
|
|
82
|
-
const {
|
|
83
|
-
timeoutMs = IO_TIMEOUT_MS,
|
|
84
|
-
} = opts;
|
|
85
|
-
const timer = new timing.Timer().start();
|
|
86
|
-
const afcService = await services.startAfcService(this.udid);
|
|
87
|
-
try {
|
|
88
|
-
let bundlePathOnPhone;
|
|
89
|
-
if ((await fs.stat(appPath)).isFile()) {
|
|
90
|
-
// https://github.com/doronz88/pymobiledevice3/blob/6ff5001f5776e03b610363254e82d7fbcad4ef5f/pymobiledevice3/services/installation_proxy.py#L75
|
|
91
|
-
bundlePathOnPhone = `/${path.basename(appPath)}`;
|
|
92
|
-
await pushFile(afcService, appPath, bundlePathOnPhone, {
|
|
93
|
-
timeoutMs,
|
|
94
|
-
});
|
|
95
|
-
} else {
|
|
96
|
-
bundlePathOnPhone = `${INSTALLATION_STAGING_DIR}/${bundleId}`;
|
|
97
|
-
await pushFolder(afcService, appPath, bundlePathOnPhone, {
|
|
98
|
-
enableParallelPush: true,
|
|
99
|
-
timeoutMs,
|
|
100
|
-
});
|
|
101
|
-
}
|
|
102
|
-
await this.installOrUpgradeApplication(
|
|
103
|
-
bundlePathOnPhone,
|
|
104
|
-
{
|
|
105
|
-
timeout: Math.max(timeoutMs - timer.getDuration().asMilliSeconds, 60000),
|
|
106
|
-
isUpgrade: await this.isAppInstalled(bundleId),
|
|
107
|
-
}
|
|
108
|
-
);
|
|
109
|
-
} catch (err) {
|
|
110
|
-
this.log.debug(err.stack);
|
|
111
|
-
let errMessage = `Cannot install the ${bundleId} application`;
|
|
112
|
-
if (err instanceof TimeoutError) {
|
|
113
|
-
errMessage += `. Consider increasing the value of 'appPushTimeout' capability (the current value equals to ${timeoutMs}ms)`;
|
|
114
|
-
}
|
|
115
|
-
errMessage += `. Original error: ${err.message}`;
|
|
116
|
-
throw new Error(errMessage);
|
|
117
|
-
} finally {
|
|
118
|
-
afcService.close();
|
|
119
|
-
}
|
|
120
|
-
this.log.info(
|
|
121
|
-
`The installation of '${bundleId}' succeeded after ${timer.getDuration().asMilliSeconds.toFixed(0)}ms`
|
|
122
|
-
);
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
/**
|
|
126
|
-
* @param {string} bundlePathOnPhone
|
|
127
|
-
* @param {InstallOrUpgradeOptions} opts
|
|
128
|
-
*/
|
|
129
|
-
async installOrUpgradeApplication(bundlePathOnPhone, {isUpgrade, timeout}) {
|
|
130
|
-
const notificationService = await services.startNotificationProxyService(this.udid);
|
|
131
|
-
const installationService = await services.startInstallationProxyService(this.udid);
|
|
132
|
-
const appInstalledNotification = new B((resolve) => {
|
|
133
|
-
notificationService.observeNotification(APPLICATION_INSTALLED_NOTIFICATION, {
|
|
134
|
-
notification: resolve,
|
|
135
|
-
});
|
|
136
|
-
});
|
|
137
|
-
const clientOptions = {PackageType: 'Developer'};
|
|
138
|
-
try {
|
|
139
|
-
if (isUpgrade) {
|
|
140
|
-
this.log.debug(
|
|
141
|
-
`An upgrade of the existing application is going to be performed. ` +
|
|
142
|
-
`Will timeout in ${timeout.toFixed(0)} ms`
|
|
143
|
-
);
|
|
144
|
-
await installationService.upgradeApplication(bundlePathOnPhone, clientOptions, timeout);
|
|
145
|
-
} else {
|
|
146
|
-
this.log.debug(
|
|
147
|
-
`A new application installation is going to be performed. ` +
|
|
148
|
-
`Will timeout in ${timeout.toFixed(0)} ms`
|
|
149
|
-
);
|
|
150
|
-
await installationService.installApplication(bundlePathOnPhone, clientOptions, timeout);
|
|
151
|
-
}
|
|
152
|
-
try {
|
|
153
|
-
await appInstalledNotification.timeout(
|
|
154
|
-
APPLICATION_NOTIFICATION_TIMEOUT_MS,
|
|
155
|
-
`Could not get the application installed notification within ` +
|
|
156
|
-
`${APPLICATION_NOTIFICATION_TIMEOUT_MS}ms but we will continue`,
|
|
157
|
-
);
|
|
158
|
-
} catch (e) {
|
|
159
|
-
this.log.warn(e.message);
|
|
160
|
-
}
|
|
161
|
-
} finally {
|
|
162
|
-
installationService.close();
|
|
163
|
-
notificationService.close();
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
/**
|
|
168
|
-
* Alias for {@linkcode install}
|
|
169
|
-
* @param {string} appPath
|
|
170
|
-
* @param {string} bundleId
|
|
171
|
-
* @param {InstallOptions} [opts={}]
|
|
172
|
-
*/
|
|
173
|
-
async installApp(appPath, bundleId, opts = {}) {
|
|
174
|
-
return await this.install(appPath, bundleId, opts);
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
/**
|
|
178
|
-
* Return an application object if test app has 'bundleid'.
|
|
179
|
-
* The target bundleid can be User and System apps.
|
|
180
|
-
*
|
|
181
|
-
* @param {string} bundleId The bundleId to ensure it is installed
|
|
182
|
-
* @return {Promise<boolean>} Returns True if the app is installed
|
|
183
|
-
* on the device under test.
|
|
184
|
-
*/
|
|
185
|
-
async isAppInstalled(bundleId) {
|
|
186
|
-
return Boolean(await this.fetchAppInfo(bundleId));
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
/**
|
|
190
|
-
* Fetches various attributes, like bundle id, version, entitlements etc. of
|
|
191
|
-
* an installed application.
|
|
192
|
-
*
|
|
193
|
-
* @param {string} bundleId the bundle identifier of an app to check
|
|
194
|
-
* @param {string|string[]|undefined} returnAttributes If provided then
|
|
195
|
-
* only fetches the requested attributes of the app into the resulting object.
|
|
196
|
-
* Some apps may have too many attributes, so it makes sense to limit these
|
|
197
|
-
* by default if you don't need all of them.
|
|
198
|
-
* @returns {Promise<Object|undefined>} Either app info as an object or undefined
|
|
199
|
-
* if the app is not found.
|
|
200
|
-
*/
|
|
201
|
-
async fetchAppInfo(bundleId, returnAttributes = ['CFBundleIdentifier', 'CFBundleVersion']) {
|
|
202
|
-
const service = await services.startInstallationProxyService(this.udid);
|
|
203
|
-
try {
|
|
204
|
-
return (
|
|
205
|
-
await service.lookupApplications({
|
|
206
|
-
bundleIds: bundleId,
|
|
207
|
-
// https://github.com/appium/appium/issues/18753
|
|
208
|
-
returnAttributes,
|
|
209
|
-
})
|
|
210
|
-
)[bundleId];
|
|
211
|
-
} finally {
|
|
212
|
-
service.close();
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
/**
|
|
217
|
-
* @param {string} bundleId
|
|
218
|
-
* @param {string} platformVersion
|
|
219
|
-
* @returns {Promise<boolean>}
|
|
220
|
-
*/
|
|
221
|
-
async terminateApp(bundleId, platformVersion) {
|
|
222
|
-
let instrumentService;
|
|
223
|
-
let installProxyService;
|
|
224
|
-
try {
|
|
225
|
-
installProxyService = await services.startInstallationProxyService(this.udid);
|
|
226
|
-
const apps = await installProxyService.listApplications({
|
|
227
|
-
returnAttributes: ['CFBundleIdentifier', 'CFBundleExecutable']
|
|
228
|
-
});
|
|
229
|
-
if (!apps[bundleId]) {
|
|
230
|
-
this.log.info(`The bundle id '${bundleId}' did not exist`);
|
|
231
|
-
return false;
|
|
232
|
-
}
|
|
233
|
-
const executableName = apps[bundleId].CFBundleExecutable;
|
|
234
|
-
this.log.debug(`The executable name for the bundle id '${bundleId}' was '${executableName}'`);
|
|
235
|
-
|
|
236
|
-
// 'devicectl' has overhead (generally?) than the instrument service via appium-ios-device,
|
|
237
|
-
// so hre uses the 'devicectl' only for iOS 17+.
|
|
238
|
-
if (util.compareVersions(platformVersion, '>=', '17.0')) {
|
|
239
|
-
this.log.debug(`Calling devicectl to kill the process`);
|
|
240
|
-
|
|
241
|
-
const pids = (await this.devicectl.listProcesses())
|
|
242
|
-
.filter(({executable}) => executable.endsWith(`/${executableName}`))
|
|
243
|
-
.map(({processIdentifier}) => processIdentifier);
|
|
244
|
-
if (_.isEmpty(pids)) {
|
|
245
|
-
this.log.info(`The process of the bundle id '${bundleId}' was not running`);
|
|
246
|
-
return false;
|
|
247
|
-
}
|
|
248
|
-
await this.devicectl.sendSignalToProcess(pids[0], 2);
|
|
249
|
-
} else {
|
|
250
|
-
instrumentService = await services.startInstrumentService(this.udid);
|
|
251
|
-
|
|
252
|
-
// The result of "runningProcesses" includes `bundle_id` key in iOS 16+ (possibly a specific 16.x+)
|
|
253
|
-
// then here may not be necessary to find a process with `CFBundleExecutable`
|
|
254
|
-
// after dropping older iOS version support.
|
|
255
|
-
const processes = await instrumentService.callChannel(
|
|
256
|
-
INSTRUMENT_CHANNEL.DEVICE_INFO,
|
|
257
|
-
'runningProcesses',
|
|
258
|
-
);
|
|
259
|
-
const process = processes.selector.find((process) => process.name === executableName);
|
|
260
|
-
if (!process) {
|
|
261
|
-
this.log.info(`The process of the bundle id '${bundleId}' was not running`);
|
|
262
|
-
return false;
|
|
263
|
-
}
|
|
264
|
-
await instrumentService.callChannel(
|
|
265
|
-
INSTRUMENT_CHANNEL.PROCESS_CONTROL,
|
|
266
|
-
'killPid:',
|
|
267
|
-
`${process.pid}`,
|
|
268
|
-
);
|
|
269
|
-
}
|
|
270
|
-
} catch (err) {
|
|
271
|
-
this.log.warn(`Failed to kill '${bundleId}'. Original error: ${err.stderr || err.message}`);
|
|
272
|
-
return false;
|
|
273
|
-
} finally {
|
|
274
|
-
if (installProxyService) {
|
|
275
|
-
installProxyService.close();
|
|
276
|
-
}
|
|
277
|
-
if (instrumentService) {
|
|
278
|
-
instrumentService.close();
|
|
279
|
-
}
|
|
280
|
-
}
|
|
281
|
-
return true;
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
/**
|
|
285
|
-
* @param {string} bundleName The name of CFBundleName in Info.plist
|
|
286
|
-
*
|
|
287
|
-
* @returns {Promise<string[]>} A list of User level apps' bundle ids which has
|
|
288
|
-
* 'CFBundleName' attribute as 'bundleName'.
|
|
289
|
-
*/
|
|
290
|
-
async getUserInstalledBundleIdsByBundleName(bundleName) {
|
|
291
|
-
const service = await services.startInstallationProxyService(this.udid);
|
|
292
|
-
try {
|
|
293
|
-
const applications = await service.listApplications({
|
|
294
|
-
applicationType: 'User', returnAttributes: ['CFBundleIdentifier', 'CFBundleName']
|
|
295
|
-
});
|
|
296
|
-
return _.reduce(
|
|
297
|
-
applications,
|
|
298
|
-
(acc, {CFBundleName}, key) => {
|
|
299
|
-
if (CFBundleName === bundleName) {
|
|
300
|
-
acc.push(key);
|
|
301
|
-
}
|
|
302
|
-
return acc;
|
|
303
|
-
},
|
|
304
|
-
/** @type {string[]} */ ([]),
|
|
305
|
-
);
|
|
306
|
-
} finally {
|
|
307
|
-
service.close();
|
|
308
|
-
}
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
/**
|
|
312
|
-
* @returns {Promise<string>}
|
|
313
|
-
*/
|
|
314
|
-
async getPlatformVersion() {
|
|
315
|
-
return await utilities.getOSVersion(this.udid);
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
/**
|
|
319
|
-
* @param {import('./driver').XCUITestDriverOpts} opts
|
|
320
|
-
* @returns {Promise<void>}
|
|
321
|
-
*/
|
|
322
|
-
async reset({bundleId, fullReset}) {
|
|
323
|
-
if (!bundleId || !fullReset || bundleId === SAFARI_BUNDLE_ID) {
|
|
324
|
-
// Safari cannot be removed as system app.
|
|
325
|
-
// Safari process handling will be managed by WDA
|
|
326
|
-
// with noReset, forceAppLaunch or shouldTerminateApp capabilities.
|
|
327
|
-
return;
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
this.log.debug(`Reset: fullReset requested. Will try to uninstall the app '${bundleId}'.`);
|
|
331
|
-
if (!(await this.isAppInstalled(bundleId))) {
|
|
332
|
-
this.log.debug('Reset: app not installed. No need to uninstall');
|
|
333
|
-
return;
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
try {
|
|
337
|
-
await this.remove(bundleId);
|
|
338
|
-
} catch (err) {
|
|
339
|
-
this.log.error(`Reset: could not remove '${bundleId}' from device: ${err.message}`);
|
|
340
|
-
throw err;
|
|
341
|
-
}
|
|
342
|
-
this.log.debug(`Reset: removed '${bundleId}'`);
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
export default RealDevice;
|
package/lib/xcrun.js
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
import {fs} from 'appium/support';
|
|
2
|
-
|
|
3
|
-
const XCRUN = 'xcrun';
|
|
4
|
-
|
|
5
|
-
async function requireXcrun() {
|
|
6
|
-
try {
|
|
7
|
-
return await fs.which(XCRUN);
|
|
8
|
-
} catch {
|
|
9
|
-
throw new Error(
|
|
10
|
-
`${XCRUN} has not been found in PATH. ` +
|
|
11
|
-
`Please make sure XCode development tools are installed`,
|
|
12
|
-
);
|
|
13
|
-
}
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export {requireXcrun, XCRUN};
|