UNPKG

appium-xcuitest-driver

Version:

Appium driver for iOS using XCUITest for backend

206 lines (180 loc) 6.12 kB
import {INSTRUMENT_CHANNEL, services} from 'appium-ios-device'; import type {AppiumLogger} from '@appium/types'; import {isIos18OrNewerPlatform} from '../utils'; import type {DVTServiceWithConnection} from 'appium-ios-remotexpc'; import type {Condition, IConditionInducer} from '../types'; import {getRemoteXPCServices} from './remotexpc-utils'; type InstrumentService = { callChannel(channel: string, method: string, ...args: any[]): Promise<{selector: any}>; close(): void; _socketClient: {destroyed: boolean}; }; /** * RemoteXPC-based implementation for iOS 18+. */ class RemoteXPCConditionInducer implements IConditionInducer { private connection: DVTServiceWithConnection | null = null; constructor( private readonly udid: string, private readonly log: AppiumLogger, ) {} async list(): Promise<Condition[]> { let connection: DVTServiceWithConnection | null = null; try { connection = await this.startConnection(); const result = await connection.conditionInducer.list(); return result as Condition[]; } catch (err: any) { this.log.error(`Failed to list condition inducers via RemoteXPC: ${err.message}`); throw err; } finally { if (connection) { this.log.info(`Closing remoteXPC connection for device ${this.udid}`); await connection.remoteXPC.close(); } } } async enable(_conditionID: string, profileID: string): Promise<boolean> { if (this.connection) { throw new Error( `Condition inducer is already running. Disable it first in order to call 'enable' again.`, ); } try { this.connection = await this.startConnection(); await this.connection.conditionInducer.set(profileID); this.log.info(`Successfully enabled condition profile: ${profileID}`); return true; } catch (err: any) { await this.close(); this.log.error(`Condition inducer '${profileID}' cannot be enabled: '${err.message}'`); throw err; } } async disable(): Promise<boolean> { if (!this.connection) { this.log.warn('Condition inducer connection is not active'); return false; } try { await this.connection.conditionInducer.disable(); this.log.info('Successfully disabled condition inducer'); return true; } catch (err: any) { this.log.warn(`Failed to disable condition inducer via RemoteXPC: ${err.message}`); return false; } finally { this.log.info(`Closing remoteXPC connection for device ${this.udid}`); await this.close(); } } async close(): Promise<void> { if (this.connection) { await this.connection.remoteXPC.close(); this.connection = null; } } isActive(): boolean { return this.connection !== null; } async startConnection(): Promise<DVTServiceWithConnection> { const Services = await getRemoteXPCServices(); return Services.startDVTService(this.udid); } } /** * Instrument service implementation (appium-ios-device) for iOS &lt; 18 and RemoteXPC fallback. */ class InstrumentConditionInducer implements IConditionInducer { private service: InstrumentService | null = null; constructor( private readonly udid: string, private readonly log: AppiumLogger, ) {} async list(): Promise<Condition[]> { const service = (await services.startInstrumentService(this.udid)) as InstrumentService; try { const ret = await service.callChannel( INSTRUMENT_CHANNEL.CONDITION_INDUCER, 'availableConditionInducers', ); return ret.selector; } finally { service.close(); } } async enable(conditionID: string, profileID: string): Promise<boolean> { if (this.service && !this.service._socketClient.destroyed) { throw new Error(`Condition inducer has been started. A condition is already active.`); } this.service = (await services.startInstrumentService(this.udid)) as InstrumentService; const ret = await this.service.callChannel( INSTRUMENT_CHANNEL.CONDITION_INDUCER, 'enableConditionWithIdentifier:profileIdentifier:', conditionID, profileID, ); if (typeof ret.selector !== 'boolean') { this.service.close(); this.service = null; throw new Error(`Enable condition inducer error: '${JSON.stringify(ret.selector)}'`); } return ret.selector; } async disable(): Promise<boolean> { if (!this.service) { this.log.warn('Condition inducer server has not started'); return false; } try { const ret = await this.service.callChannel( INSTRUMENT_CHANNEL.CONDITION_INDUCER, 'disableActiveCondition', ); if (typeof ret.selector !== 'boolean') { this.log.warn(`Disable condition inducer error: '${JSON.stringify(ret.selector)}'`); return false; } return ret.selector; } finally { if (this.service) { this.service.close(); this.service = null; } } } async close(): Promise<void> { if (this.service) { this.service.close(); this.service = null; } } isActive(): boolean { return this.service !== null && !this.service._socketClient.destroyed; } } /** * Picks RemoteXPC when the platform is iOS/tvOS 18+ and probe succeeds; otherwise legacy instrument service. */ export async function createConditionInducer(params: { udid: string; log: AppiumLogger; platformVersion?: string; }): Promise<IConditionInducer> { const {udid, log, platformVersion} = params; if (!isIos18OrNewerPlatform(platformVersion)) { return new InstrumentConditionInducer(udid, log); } const xpcInducer = new RemoteXPCConditionInducer(udid, log); try { const connection = await xpcInducer.startConnection(); await connection.remoteXPC.close(); } catch (err: any) { log.warn( `Unable to use RemoteXPC-based condition inducer for device ${udid}, ` + `falling back to the legacy implementation: ${err.message}`, ); return new InstrumentConditionInducer(udid, log); } return xpcInducer; }