appium-xcuitest-driver
Version:
Appium driver for iOS using XCUITest for backend
216 lines • 8.17 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.InstallationProxyClient = void 0;
const remotexpc_utils_1 = require("./remotexpc-utils");
const logger_1 = require("../logger");
const appium_ios_device_1 = require("appium-ios-device");
/**
* Unified Installation Proxy Client
*
* Provides a unified interface for app installation/management operations on iOS devices
*/
class InstallationProxyClient {
service;
remoteXPCConnection;
constructor(service, remoteXPCConnection) {
this.service = service;
this.remoteXPCConnection = remoteXPCConnection;
}
/**
* Check if this client is using RemoteXPC
*/
get isRemoteXPC() {
return !!this.remoteXPCConnection;
}
/**
* Get the RemoteXPC service (throws if not RemoteXPC)
*/
get remoteXPCService() {
return this.service;
}
/**
* Get the ios-device service (throws if not ios-device)
*/
get iosDeviceService() {
return this.service;
}
//#region Public Methods
/**
* Create an InstallationProxy client for the device
*
* @param udid - Device UDID
* @param useRemoteXPC - Whether to use RemoteXPC
* @returns InstallationProxy client instance
*/
static async create(udid, useRemoteXPC) {
if (useRemoteXPC) {
const client = await InstallationProxyClient.withRemoteXpcConnection(async () => {
const Services = await (0, remotexpc_utils_1.getRemoteXPCServices)();
const { installationProxyService, remoteXPC } = await Services.startInstallationProxyService(udid);
return {
service: installationProxyService,
connection: remoteXPC,
};
});
if (client) {
return client;
}
}
const service = await appium_ios_device_1.services.startInstallationProxyService(udid);
return new InstallationProxyClient(service);
}
/**
* Helper to safely execute RemoteXPC operations with connection cleanup
*/
static async withRemoteXpcConnection(operation) {
let remoteXPCConnection;
let succeeded = false;
try {
const { service, connection } = await operation();
remoteXPCConnection = connection;
const client = new InstallationProxyClient(service, remoteXPCConnection);
succeeded = true;
return client;
}
catch (err) {
logger_1.log.error(`Failed to create InstallationProxy client via RemoteXPC: ${err.message}, falling back to appium-ios-device`);
return null;
}
finally {
// Only close connection if we failed (if succeeded, the client owns it)
if (!succeeded && remoteXPCConnection) {
try {
await remoteXPCConnection.close();
}
catch (closeErr) {
logger_1.log.debug(`Error closing RemoteXPC connection during cleanup: ${closeErr.message}`);
}
}
}
}
/**
* List installed applications
*
* @param opts - Options for filtering and selecting attributes
* @returns Object keyed by bundle ID
*/
async listApplications(opts) {
let normalizedOpts = opts;
// Ensure CFBundleIdentifier is always included
if (opts?.returnAttributes && !opts.returnAttributes.includes('CFBundleIdentifier')) {
normalizedOpts = {
...opts,
returnAttributes: ['CFBundleIdentifier', ...opts.returnAttributes],
};
}
if (!this.isRemoteXPC) {
return await this.iosDeviceService.listApplications(normalizedOpts);
}
// RemoteXPC returns array, need to convert to object
const apps = await this.remoteXPCService.browse({
applicationType: normalizedOpts?.applicationType || 'Any',
// Use '*' to request all attributes when returnAttributes is not explicitly specified
returnAttributes: normalizedOpts?.returnAttributes || '*',
});
// Convert array to object keyed by CFBundleIdentifier
return apps.reduce((acc, app) => {
if (app.CFBundleIdentifier) {
acc[app.CFBundleIdentifier] = app;
}
return acc;
}, {});
}
/**
* Look up application information for specific bundle IDs
*
* @param opts - Bundle IDs and options
* @returns Object keyed by bundle ID
*/
async lookupApplications(opts) {
if (!this.isRemoteXPC) {
return await this.iosDeviceService.lookupApplications(opts);
}
const bundleIds = Array.isArray(opts.bundleIds) ? opts.bundleIds : [opts.bundleIds];
return (await this.remoteXPCService.lookup(bundleIds, {
returnAttributes: opts.returnAttributes,
applicationType: opts.applicationType,
}));
}
/**
* Install an application
*
* @param path - Path to ipa
* @param clientOptions - Installation options
* @param timeoutMs - Timeout in milliseconds
* @returns Array of progress messages received during installation
*/
async installApplication(path, clientOptions, timeoutMs) {
if (!this.isRemoteXPC) {
return await this.iosDeviceService.installApplication(path, clientOptions, timeoutMs);
}
return await this.executeWithProgressCollection((progressHandler) => this.remoteXPCService.install(path, { ...clientOptions, timeoutMs }, progressHandler));
}
/**
* Upgrade an application
*
* @param path - Path to app on device
* @param clientOptions - Installation options
* @param timeoutMs - Timeout in milliseconds
* @returns Array of progress messages received during upgrade
*/
async upgradeApplication(path, clientOptions, timeoutMs) {
if (!this.isRemoteXPC) {
return await this.iosDeviceService.upgradeApplication(path, clientOptions, timeoutMs);
}
return await this.executeWithProgressCollection((progressHandler) => this.remoteXPCService.upgrade(path, { ...clientOptions, timeoutMs }, progressHandler));
}
/**
* Uninstall an application
*
* @param bundleId - Bundle ID of app to uninstall
* @param timeoutMs - Timeout in milliseconds
* @returns Array of progress messages received during uninstallation
*/
async uninstallApplication(bundleId, timeoutMs) {
if (!this.isRemoteXPC) {
return await this.iosDeviceService.uninstallApplication(bundleId, timeoutMs);
}
return await this.executeWithProgressCollection((progressHandler) => this.remoteXPCService.uninstall(bundleId, { timeoutMs }, progressHandler));
}
/**
* Close the client and cleanup resources
*/
async close() {
try {
this.service.close();
}
catch (err) {
logger_1.log.debug(`Error closing installation proxy service: ${err.message}`);
}
if (this.remoteXPCConnection) {
try {
await this.remoteXPCConnection.close();
}
catch (err) {
logger_1.log.warn(`Error closing RemoteXPC connection: ${err.message}`);
}
}
}
//#endregion
//#region Private Methods
/**
* Execute a RemoteXPC operation and collect progress messages to match ios-device behavior
*
* @param operation - Function that executes the RemoteXPC operation with a progress handler
* @returns Array of progress messages
*/
async executeWithProgressCollection(operation) {
const messages = [];
await operation((percentComplete, status) => {
messages.push({ PercentComplete: percentComplete, Status: status });
});
return messages;
}
}
exports.InstallationProxyClient = InstallationProxyClient;
//# sourceMappingURL=installation-proxy-client.js.map