appium-xcuitest-driver
Version:
Appium driver for iOS using XCUITest for backend
257 lines • 10.2 kB
JavaScript
;
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