UNPKG

appium-xcuitest-driver

Version:

Appium driver for iOS using XCUITest for backend

205 lines 7.81 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.InstallationProxyClient = void 0; const appium_ios_device_1 = require("appium-ios-device"); const logger_1 = require("../logger"); /** * Unified Installation Proxy Client * * Provides a unified interface for app installation/management operations on iOS devices */ class InstallationProxyClient { service; _isRemoteXPC; _log; _lastLoggedProgress; constructor(service, _isRemoteXPC, _log = logger_1.log) { this.service = service; this._isRemoteXPC = _isRemoteXPC; this._log = _log; } /** * Check if this client is using RemoteXPC */ get isRemoteXPC() { return this._isRemoteXPC; } /** * 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 opts - Creation options * @returns InstallationProxy client instance */ static async create(udid, opts = {}) { const { allowLegacyFallback = true, facade = null, logger } = opts; const service = facade ? await facade.attemptService('InstallationProxy', (Services) => Services.startInstallationProxyService(udid)) : null; if (service) { return new InstallationProxyClient(service, true, logger); } if (!allowLegacyFallback) { throw new Error(`InstallationProxy access via RemoteXPC is required for '${udid}', but it is unavailable.`); } const legacyService = await appium_ios_device_1.services.startInstallationProxyService(udid); return new InstallationProxyClient(legacyService, false, logger); } /** * 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 */ async installApplication(path, clientOptions, timeoutMs) { if (!this.isRemoteXPC) { const messages = await this.iosDeviceService.installApplication(path, clientOptions, timeoutMs); this.logProgressBatch('install', messages); return; } await this.executeWithProgressLogging('install', (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 */ async upgradeApplication(path, clientOptions, timeoutMs) { if (!this.isRemoteXPC) { const messages = await this.iosDeviceService.upgradeApplication(path, clientOptions, timeoutMs); this.logProgressBatch('upgrade', messages); return; } await this.executeWithProgressLogging('upgrade', (progressHandler) => this.remoteXPCService.upgrade(path, { ...clientOptions, timeoutMs }, progressHandler)); } /** * Uninstall an application * * @param bundleId - Bundle ID of app to uninstall * @param timeoutMs - Timeout in milliseconds */ async uninstallApplication(bundleId, timeoutMs) { if (!this.isRemoteXPC) { await this.iosDeviceService.uninstallApplication(bundleId, timeoutMs); return; } await this.executeWithProgressLogging('uninstall', (progressHandler) => this.remoteXPCService.uninstall(bundleId, { timeoutMs }, progressHandler)); } /** * Close the client and cleanup resources */ async close() { try { this.service.close(); } catch (err) { this._log.debug(`Error closing installation proxy service: ${err.message}`); } } //#endregion //#region Private Methods /** * Execute a RemoteXPC operation and log progress messages as they arrive * * @param operation - Function that executes the RemoteXPC operation with a progress handler */ async executeWithProgressLogging(progressOperation, operation) { this._lastLoggedProgress = undefined; await operation((percentComplete, status) => { this.logProgress(progressOperation, { PercentComplete: percentComplete, Status: status }); }); } logProgressBatch(progressOperation, messages) { this._lastLoggedProgress = undefined; for (const message of messages) { this.logProgress(progressOperation, message); } } logProgress(progressOperation, message) { const prefix = `App ${progressOperation} progress`; if (message.Error) { this._log.warn(`${prefix} error: ${message.Error}` + (message.ErrorDescription ? ` (${message.ErrorDescription})` : '')); return; } const { PercentComplete: percentComplete, Status: status } = message; if (percentComplete === undefined && !status) { return; } if (percentComplete === this._lastLoggedProgress?.percent && status === this._lastLoggedProgress?.status) { return; } this._lastLoggedProgress = { percent: percentComplete, status }; if (percentComplete !== undefined) { this._log.debug(`${prefix}: ${percentComplete}%${status ? ` (${status})` : ''}`); } else if (status) { this._log.debug(`${prefix}: ${status}`); } } } exports.InstallationProxyClient = InstallationProxyClient; //# sourceMappingURL=installation-proxy-client.js.map