UNPKG

appium-mac2-driver

Version:

XCTest-based Appium driver for macOS apps automation

282 lines (244 loc) 9.2 kB
import _ from 'lodash'; import type { RouteMatcher, HTTPMethod, HTTPBody, DefaultCreateSessionResult, DriverData, InitialOpts, StringRecord, ExternalDriver, DriverCaps, DriverOpts, W3CDriverCaps, } from '@appium/types'; import {BaseDriver, DeviceSettings} from 'appium/driver'; import {WDA_MAC_SERVER, type WDAMacServer} from './wda-mac'; import MAC2_CONSTRAINTS, {type Mac2Constraints} from './constraints'; import * as appManagemenetCommands from './commands/app-management'; import * as appleScriptCommands from './commands/applescript'; import * as executeCommands from './commands/execute'; import * as findCommands from './commands/find'; import * as gesturesCommands from './commands/gestures'; import * as navigationCommands from './commands/navigation'; import * as recordScreenCommands from './commands/record-screen'; import * as screenshotCommands from './commands/screenshots'; import * as sourceCommands from './commands/source'; import * as clipboardCommands from './commands/clipboard'; import * as nativeScreenRecordingCommands from './commands/native-record-screen'; import log from './logger'; import {newMethodMap} from './method-map'; import {executeMethodMap} from './execute-method-map'; const NO_PROXY: RouteMatcher[] = [ ['GET', new RegExp('^/session/[^/]+/appium')], ['POST', new RegExp('^/session/[^/]+/appium')], ['POST', new RegExp('^/session/[^/]+/element/[^/]+/elements?$')], ['POST', new RegExp('^/session/[^/]+/elements?$')], ['POST', new RegExp('^/session/[^/]+/execute')], ['POST', new RegExp('^/session/[^/]+/execute/sync')], ['GET', new RegExp('^/session/[^/]+/timeouts$')], ['POST', new RegExp('^/session/[^/]+/timeouts$')], ]; export class Mac2Driver extends BaseDriver<Mac2Constraints, StringRecord> implements ExternalDriver<Mac2Constraints, string, StringRecord> { private isProxyActive: boolean; private _wda: WDAMacServer | null; _videoChunksBroadcaster: nativeScreenRecordingCommands.NativeVideoChunksBroadcaster; _screenRecorder: recordScreenCommands.ScreenRecorder | null; public proxyReqRes: (...args: any) => any; static newMethodMap = newMethodMap; static executeMethodMap = executeMethodMap; constructor(opts: InitialOpts = {} as InitialOpts) { super(opts); this.desiredCapConstraints = _.cloneDeep(MAC2_CONSTRAINTS); this.locatorStrategies = [ 'id', 'name', 'accessibility id', 'xpath', 'class name', '-ios predicate string', 'predicate string', '-ios class chain', 'class chain', ]; this.resetState(); this.settings = new DeviceSettings({}, this.onSettingsUpdate.bind(this)); } async onSettingsUpdate(key: string, value: unknown): Promise<void> { if (!this._wda) { return; } return await this._wda.proxy.command('/appium/settings', 'POST', { settings: {[key]: value}, }); } get wda(): WDAMacServer { if (!this._wda) { throw new Error('WDA server is not initialized'); } return this._wda; } // eslint-disable-next-line @typescript-eslint/no-unused-vars override proxyActive(sessionId: string): boolean { return this.isProxyActive; } // eslint-disable-next-line @typescript-eslint/no-unused-vars override getProxyAvoidList(sessionId: string): RouteMatcher[] { return NO_PROXY; } override canProxy(): boolean { return true; } async proxyCommand(url: string, method: HTTPMethod, body: HTTPBody = null): Promise<any> { if (!this._wda) { throw new Error('WDA server is not initialized'); } return await this._wda.proxy.command(url, method, body); } override async getStatus(): Promise<any> { if (!this._wda) { throw new Error('WDA server is not initialized'); } return await this._wda.proxy.command('/status', 'GET'); } // needed to make image plugin work async getWindowRect(): Promise<any> { if (!this._wda) { throw new Error('WDA server is not initialized'); } return await this._wda.proxy.command('/window/rect', 'GET'); } override async createSession( w3cCaps1: W3CMac2DriverCaps, w3cCaps2?: W3CMac2DriverCaps, w3cCaps3?: W3CMac2DriverCaps, driverData?: DriverData[], ): Promise<DefaultCreateSessionResult<Mac2Constraints>> { const [sessionId, caps] = await super.createSession(w3cCaps1, w3cCaps2, w3cCaps3, driverData); this._wda = WDA_MAC_SERVER; this.caps = caps as Mac2DriverCaps; this.opts = this.opts as Mac2DriverOpts; try { const prerun = caps.prerun as PrerunCapability | undefined; if (prerun) { if (!_.isString(prerun.command) && !_.isString(prerun.script)) { throw new Error( `'prerun' capability value must either contain ` + `'script' or 'command' entry of string type`, ); } log.info('Executing prerun AppleScript'); const output = await this.macosExecAppleScript(prerun.script, undefined, prerun.command); if (_.trim(output)) { log.info(`Prerun script output: ${output}`); } } await this._wda.startSession(caps, { reqBasePath: this.basePath, }); } catch (e: any) { await this.deleteSession(); throw e; } this.proxyReqRes = this.wda.proxy.proxyReqRes.bind(this._wda.proxy); this.isProxyActive = true; return [sessionId, caps]; } override async deleteSession(): Promise<void> { await this._screenRecorder?.stop(true); if (this._videoChunksBroadcaster.hasPublishers) { if (this._wda) { try { await this.wda.proxy.command('/wda/video/stop', 'POST', {}); } catch {} } await this._videoChunksBroadcaster.shutdown(5000); } if (this._wda) { await this.wda.stopSession(); } const postrun = this.opts.postrun as PostrunCapability | undefined; if (postrun) { if (!_.isString(postrun.command) && !_.isString(postrun.script)) { log.error( `'postrun' capability value must either contain ` + `'script' or 'command' entry of string type`, ); } else { log.info('Executing postrun AppleScript'); try { const output = await this.macosExecAppleScript( postrun.script, undefined, postrun.command, ); if (_.trim(output)) { log.info(`Postrun script output: ${output}`); } } catch (e: any) { log.error(e.message); } } } this.resetState(); await super.deleteSession(); } private resetState(): void { this._wda = null; this.isProxyActive = false; this._videoChunksBroadcaster = new nativeScreenRecordingCommands.NativeVideoChunksBroadcaster( this.eventEmitter, this.log, ); this._screenRecorder = null; } macosLaunchApp = appManagemenetCommands.macosLaunchApp; macosActivateApp = appManagemenetCommands.macosActivateApp; macosTerminateApp = appManagemenetCommands.macosTerminateApp; macosQueryAppState = appManagemenetCommands.macosQueryAppState; macosExecAppleScript = appleScriptCommands.macosExecAppleScript; execute = executeCommands.execute; findElOrEls = findCommands.findElOrEls; macosSetValue = gesturesCommands.macosSetValue; macosClick = gesturesCommands.macosClick; macosScroll = gesturesCommands.macosScroll; macosSwipe = gesturesCommands.macosSwipe; macosRightClick = gesturesCommands.macosRightClick; macosHover = gesturesCommands.macosHover; macosDoubleClick = gesturesCommands.macosDoubleClick; macosClickAndDrag = gesturesCommands.macosClickAndDrag; macosClickAndDragAndHold = gesturesCommands.macosClickAndDragAndHold; macosKeys = gesturesCommands.macosKeys; macosPressAndHold = gesturesCommands.macosPressAndHold; macosTap = gesturesCommands.macosTap; macosDoubleTap = gesturesCommands.macosDoubleTap; macosPressAndDrag = gesturesCommands.macosPressAndDrag; macosPressAndDragAndHold = gesturesCommands.macosPressAndDragAndHold; macosGetClipboard = clipboardCommands.macosGetClipboard; macosSetClipboard = clipboardCommands.macosSetClipboard; macosDeepLink = navigationCommands.macosDeepLink; startRecordingScreen = recordScreenCommands.startRecordingScreen; stopRecordingScreen = recordScreenCommands.stopRecordingScreen; macosStartNativeScreenRecording = nativeScreenRecordingCommands.macosStartNativeScreenRecording; macosGetNativeScreenRecordingInfo = nativeScreenRecordingCommands.macosGetNativeScreenRecordingInfo; macosStopNativeScreenRecording = nativeScreenRecordingCommands.macosStopNativeScreenRecording; macosListDisplays = nativeScreenRecordingCommands.macosListDisplays; macosScreenshots = screenshotCommands.macosScreenshots; macosSource = sourceCommands.macosSource; } export default Mac2Driver; interface PrerunCapability { command?: string; script?: string; } interface PostrunCapability { command?: string; script?: string; } type Mac2DriverOpts = DriverOpts<Mac2Constraints>; type Mac2DriverCaps = DriverCaps<Mac2Constraints>; type W3CMac2DriverCaps = W3CDriverCaps<Mac2Constraints>;