UNPKG

appium-xcuitest-driver

Version:

Appium driver for iOS using XCUITest for backend

216 lines 8.17 kB
"use strict"; 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