UNPKG

appium-xcuitest-driver

Version:

Appium driver for iOS using XCUITest for backend

257 lines 10.2 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.isTunnelAvailabilityError = isTunnelAvailabilityError; exports.getRemoteXPCServices = getRemoteXPCServices; exports.tryGetRemoteXPCServices = tryGetRemoteXPCServices; exports.isRemoteXPCOptionalDependencyMissing = isRemoteXPCOptionalDependencyMissing; exports.getLastRemoteXPCOptionalImportError = getLastRemoteXPCOptionalImportError; exports.tryGetRemoteXPCModule = tryGetRemoteXPCModule; exports.tryGetRemoteXPCUsbMuxStrategy = tryGetRemoteXPCUsbMuxStrategy; exports.getXCTestRunnerClass = getXCTestRunnerClass; const support_1 = require("appium/support"); const node_path_1 = __importDefault(require("node:path")); const node_fs_1 = require("node:fs"); const usbmux_utils_1 = require("./usbmux-utils"); /** * Whether the given error means RemoteXPC tunnel infrastructure is unavailable. */ function isTunnelAvailabilityError(err) { if (!err) { return false; } const name = err?.name ?? err?.constructor?.name; return name === 'TunnelAvailabilityError'; } /** * Full ESM namespace after a successful `import('appium-ios-remotexpc')` (e.g. **XCTestAttachment**). * Set together with {@link cachedRemoteXPCServices}. */ let cachedRemoteXPCFullModule = null; /** * Cached RemoteXPC Services module */ let cachedRemoteXPCServices = null; /** * Set when **appium-ios-remotexpc** resolution failed in a way that is unlikely to succeed on * retry in the same process (package not installed). Transient import errors do not set this. */ let remoteXpcModuleUnavailable = false; /** Stored only when {@link remoteXpcModuleUnavailable} is set (missing-package case). */ let lastRemoteXpcImportError = null; /** * Most recent failed optional `import('appium-ios-remotexpc')` from {@link tryGetRemoteXPCServices} * (including full-module backfill). Cleared when an optional load succeeds. */ let lastTryGetRemoteXPCImportError = null; /** * Cached XCTestRunner class */ let cachedXCTestRunnerClass = null; /** * Module root and version cached at initialization */ const { moduleRoot, remoteXpcVersion } = fetchInstallInfo(); /** * Get the RemoteXPC Services module dynamically * * This helper centralizes the import of appium-ios-remotexpc to: * - Provide consistent error handling across all services * - Give helpful installation instructions when the module is missing * * @returns The Services export from appium-ios-remotexpc * @throws {Error} If the module cannot be imported */ async function getRemoteXPCServices() { if (cachedRemoteXPCServices) { if (!cachedRemoteXPCFullModule) { try { cachedRemoteXPCFullModule = (await import('appium-ios-remotexpc')); } catch (err) { throwRemoteXPCImportError(err); } } lastTryGetRemoteXPCImportError = null; return cachedRemoteXPCServices; } if (remoteXpcModuleUnavailable && lastRemoteXpcImportError) { throwRemoteXPCImportError(lastRemoteXpcImportError); } try { const remotexpcModule = (await import('appium-ios-remotexpc')); cachedRemoteXPCFullModule = remotexpcModule; cachedRemoteXPCServices = remotexpcModule.Services; lastTryGetRemoteXPCImportError = null; return cachedRemoteXPCServices; } catch (err) { throwRemoteXPCImportError(err); } } /** * Try to load appium-ios-remotexpc without throwing (e.g. for optional features). * Successful loads share the same cache as {@link getRemoteXPCServices}. * * If the package is **not installed** (resolution error for **appium-ios-remotexpc**), subsequent * calls return `null` without re-importing. Other import failures are recorded via * {@link getLastRemoteXPCOptionalImportError} and **do not** permanently disable retries. */ async function tryGetRemoteXPCServices() { if (cachedRemoteXPCServices) { if (!cachedRemoteXPCFullModule) { try { cachedRemoteXPCFullModule = (await import('appium-ios-remotexpc')); lastTryGetRemoteXPCImportError = null; } catch (err) { lastTryGetRemoteXPCImportError = err; /* ignore: tryGetRemoteXPCModule may still return null for XCTestAttachment callers */ } } else { lastTryGetRemoteXPCImportError = null; } return cachedRemoteXPCServices; } if (remoteXpcModuleUnavailable) { return null; } try { const remotexpcModule = (await import('appium-ios-remotexpc')); cachedRemoteXPCFullModule = remotexpcModule; cachedRemoteXPCServices = remotexpcModule.Services; lastTryGetRemoteXPCImportError = null; return cachedRemoteXPCServices; } catch (err) { const error = err; lastTryGetRemoteXPCImportError = error; if (isAppiumIosRemotexpcPackageMissingError(error)) { lastRemoteXpcImportError = error; remoteXpcModuleUnavailable = true; } return null; } } /** * Whether {@link tryGetRemoteXPCServices} has determined that **appium-ios-remotexpc** is not * installed (same process will not retry optional import). */ function isRemoteXPCOptionalDependencyMissing() { return remoteXpcModuleUnavailable; } /** * Last error from an optional RemoteXPC `import()`, including transient failures. Cleared when a * load succeeds. When {@link isRemoteXPCOptionalDependencyMissing} is `true`, this matches the * stored missing-package error. */ function getLastRemoteXPCOptionalImportError() { return lastTryGetRemoteXPCImportError; } /** * Full **appium-ios-remotexpc** module after a successful optional load (same `import()` as * {@link tryGetRemoteXPCServices}). Returns `null` if the package is missing or failed to load. */ async function tryGetRemoteXPCModule() { await tryGetRemoteXPCServices(); return cachedRemoteXPCFullModule; } /** * Optional load of **appium-ios-remotexpc** (shared cache) plus the USBMUX vs tunnel branch hint: * whether `udid` appears in the usbmux device list. Used by lockdown and port forwarding so they * do not duplicate `import()` + {@link isDeviceListedInUsbmux}. * * @returns `null` if the module is not available; otherwise the module and whether to use the * USBMUX-oriented APIs (`createLockdownServiceByUDID`, `connectViaUsbmux`, …). */ async function tryGetRemoteXPCUsbMuxStrategy(udid, log) { const remotexpc = await tryGetRemoteXPCModule(); if (!remotexpc) { return null; } const useUsbMuxPath = await (0, usbmux_utils_1.isDeviceListedInUsbmux)(remotexpc, udid, log); return { remotexpc, useUsbMuxPath }; } /** * Get the XCTestRunner class dynamically from appium-ios-remotexpc * * @returns The XCTestRunner class * @throws {Error} If the module cannot be imported */ async function getXCTestRunnerClass() { if (cachedXCTestRunnerClass) { return cachedXCTestRunnerClass; } await getRemoteXPCServices(); const remotexpcModule = cachedRemoteXPCFullModule; if (!remotexpcModule) { throw new Error('appium-ios-remotexpc loaded Services but full module cache is missing; cannot load XCTestRunner.'); } try { const XCTestRunnerClass = remotexpcModule.XCTestRunner; if (typeof XCTestRunnerClass !== 'function') { throw new Error('XCTestRunner is not exported from appium-ios-remotexpc. ' + 'The installed version may be incompatible.'); } cachedXCTestRunnerClass = XCTestRunnerClass; return cachedXCTestRunnerClass; } catch (err) { throw new Error('Failed to import XCTestRunner from appium-ios-remotexpc. ' + `Original error: ${err.message}`, { cause: err }); } } /** * Whether `err` indicates the **appium-ios-remotexpc** package is not installed / not resolvable, * as opposed to a transient or corrupt-install failure that may succeed on a later attempt. */ function isAppiumIosRemotexpcPackageMissingError(err) { const msg = err.message; return ((msg.includes('Cannot find module') && msg.includes('appium-ios-remotexpc')) || (msg.includes('Cannot find package') && msg.includes('appium-ios-remotexpc'))); } function throwRemoteXPCImportError(err) { if (err.message.includes('Cannot find module')) { let errorMessage = 'Failed to import appium-ios-remotexpc module. ' + 'This module is required for iOS 18 and above device operations.'; if (moduleRoot && remoteXpcVersion) { errorMessage += ' Please install it by running: ' + `cd "${moduleRoot}" && npm install "appium-ios-remotexpc@${remoteXpcVersion}".`; } errorMessage += ` Original error: ${err.message}`; throw new Error(errorMessage); } throw new Error('Failed to import appium-ios-remotexpc module. ' + 'This module is required for iOS 18 and above device operations. ' + `Original error: ${err.message}`); } /** * Fetch module root and appium-ios-remotexpc version from package.json * * @returns Object containing moduleRoot and remoteXpcVersion */ function fetchInstallInfo() { try { const root = support_1.node.getModuleRootSync('appium-xcuitest-driver', __filename); if (root) { const packageJsonPath = node_path_1.default.join(root, 'package.json'); const packageJsonContent = (0, node_fs_1.readFileSync)(packageJsonPath, 'utf8'); if (packageJsonContent) { const packageJson = JSON.parse(packageJsonContent); return { moduleRoot: root, remoteXpcVersion: packageJson.optionalDependencies?.['appium-ios-remotexpc'], }; } } } catch { // Error messages will skip install hints } return { moduleRoot: undefined, remoteXpcVersion: undefined }; } //# sourceMappingURL=remotexpc-utils.js.map