UNPKG

appium-xcuitest-driver

Version:

Appium driver for iOS using XCUITest for backend

361 lines 16.3 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.start = start; exports.startWdaSession = startWdaSession; const driver_1 = require("appium/driver"); const support_1 = require("appium/support"); const asyncbox_1 = require("asyncbox"); const utils_1 = require("../../utils"); const node_path_1 = __importDefault(require("node:path")); const real_device_management_1 = require("../../device/real-device-management"); const simulator_management_1 = require("../../device/simulator-management"); const helpers_1 = require("../helpers"); const cleanup_1 = require("./cleanup"); const constants_1 = require("./constants"); /** * Initializes the WebDriverAgent connection, prepares the device, and starts WDA. */ async function start() { await setupConnection(this); const synchronizationKey = await getSynchronizationKey(this); logSynchronizationDetails(this, synchronizationKey); assertUsePreinstalledWdaSupported(this); await assertPrebuiltPathExists(this); return await constants_1.SHARED_RESOURCES_GUARD.acquire(synchronizationKey, async () => { await startUnderSynchronizationLock(this); }); } /** * Creates a WebDriverAgent session with the given application bundle and process arguments. */ async function startWdaSession(bundleId, processArguments) { return createWdaSession(this, bundleId, processArguments); } /** * Prepares a preinstalled WebDriverAgent bundle on the device before launch. */ async function preparePreinstalled(driver) { if (driver.isRealDevice()) { await driver.mobileKillApp(driver.wda.bundleIdForXctest); } if (!driver.opts.prebuiltWDAPath) { await cleanupApps(driver, [driver.wda.bundleIdForXctest]); return; } const candidateBundleId = await driver.appInfosCache.extractBundleId(driver.opts.prebuiltWDAPath); await cleanupApps(driver, [candidateBundleId]); driver.wda.updatedWDABundleId = candidateBundleId.replace('.xctrunner', ''); driver.log.info(`Installing prebuilt WDA at '${driver.opts.prebuiltWDAPath}'. Bundle identifier: ${candidateBundleId}.`); if (driver.isRealDevice()) { await real_device_management_1.installToRealDevice.bind(driver)(driver.opts.prebuiltWDAPath, candidateBundleId, { skipUninstall: true, timeout: driver.opts.appPushTimeout, }); } else { await simulator_management_1.installToSimulator.bind(driver)(driver.opts.prebuiltWDAPath, candidateBundleId); } } /** * Removes WebDriverAgent runner apps that share the same CFBundleName except those listed in `keepBundleIds`. * * @param driver - The driver instance * @param keepBundleIds - Bundle identifiers to preserve on the device */ async function cleanupApps(driver, keepBundleIds = []) { const installedBundleIds = await driver.device.getUserInstalledBundleIdsByBundleName(constants_1.WDA_CF_BUNDLE_NAME); const keep = new Set(keepBundleIds.filter(Boolean)); for (const bundleId of installedBundleIds.filter((id) => !keep.has(id))) { driver.log.info(`Removing WebDriverAgent runner app '${bundleId}' ` + `(CFBundleName '${constants_1.WDA_CF_BUNDLE_NAME}')`); try { await driver.device.removeApp(bundleId); } catch (e) { driver.log.warn(`Failed to remove WebDriverAgent apps: ${e.message}`); } } } async function setupConnection(driver) { if (!support_1.util.hasValue(driver.wda.webDriverAgentUrl)) { await driver.wda.cleanupObsoleteProcesses(); } const usePortForwarding = driver.isRealDevice() && !driver.wda.webDriverAgentUrl && (0, helpers_1.isLocalHost)(driver.wda.wdaBaseUrl); await driver.deviceConnectionsFactory.requestConnection(driver.opts.udid, Number(driver.wda.url.port), { devicePort: usePortForwarding ? driver.wda.wdaRemotePort : null, platformVersion: driver.opts.platformVersion, usePortForwarding, }); } async function getSynchronizationKey(driver) { if (driver.opts.useXctestrunFile || !(await driver.wda.isSourceFresh())) { const derivedDataPath = await driver.wda.retrieveDerivedDataPath(); if (derivedDataPath) { return node_path_1.default.normalize(derivedDataPath); } } return constants_1.XCUITEST_DRIVER_SYNC_NAME; } function logSynchronizationDetails(driver, synchronizationKey) { driver.log.debug(`Starting WebDriverAgent initialization with the synchronization key '${synchronizationKey}'`); if (constants_1.SHARED_RESOURCES_GUARD.isBusy() && !driver.opts.derivedDataPath && !driver.opts.bootstrapPath) { driver.log.debug(`Consider setting a unique 'derivedDataPath' capability value for each parallel driver instance ` + `to avoid conflicts and speed up the building process`); } } function assertUsePreinstalledWdaSupported(driver) { if (!driver.opts.usePreinstalledWDA) { return; } const { platformVersion } = driver.opts; if (!(0, helpers_1.isIos17OrNewerPlatform)(platformVersion)) { throw new Error(`The 'usePreinstalledWDA' capability is only supported on iOS/tvOS 17.0 and newer ` + `(the current 'platformVersion' capability value is '${platformVersion}'). ` + `WebDriverAgent v13 no longer uses the legacy XCTest launch path that was required on iOS 16 and below. ` + `Use the default xcodebuild flow or provide 'webDriverAgentUrl' instead.`); } } async function assertPrebuiltPathExists(driver) { if (driver.opts.usePreinstalledWDA && driver.opts.prebuiltWDAPath && !(await support_1.fs.exists(driver.opts.prebuiltWDAPath))) { throw new Error(`The prebuilt WebDriverAgent app at '${driver.opts.prebuiltWDAPath}' provided as 'prebuiltWDAPath' ` + `capability value does not exist or is not accessible`); } } async function startUnderSynchronizationLock(driver) { await prepareForXcodebuild(driver); if (driver.opts.resultBundlePath) { driver.assertFeatureEnabled(constants_1.CUSTOMIZE_RESULT_BUNDLE_PATH); } const { startupRetries, startupRetryInterval } = getStartupRetryOptions(driver); await runStartupWithRetries(driver, startupRetries, startupRetryInterval); } async function prepareForXcodebuild(driver) { if (!(0, constants_1.isXcodebuildNeeded)(driver.opts)) { return; } if (driver.opts.useNewWDA) { driver.log.debug(`Capability 'useNewWDA' set to true, so quitting and uninstalling WDA before proceeding`); await driver.wda.quit(); await cleanupApps(driver); driver.logEvent('wdaUninstalled'); return; } if (await driver.wda.setupCaching()) { return; } // Cleanup only WDA runners whose bundle identifiers are not the current one const bundleIdsToKeep = []; if (driver.opts.updatedWDABundleId) { bundleIdsToKeep.push(driver.wda.bundleIdForXctest); } else { const currentRunnerBundleId = (await driver.wda.retrieveBuildSettings({ scheme: 'WebDriverAgentRunner', }))?.PRODUCT_BUNDLE_IDENTIFIER; if (currentRunnerBundleId) { bundleIdsToKeep.push(`${currentRunnerBundleId}.xctrunner`); } } await cleanupApps(driver, bundleIdsToKeep); } function getStartupRetryOptions(driver) { let startupRetries = driver.opts.wdaStartupRetries || (driver.isRealDevice() ? constants_1.WDA_REAL_DEV_STARTUP_RETRIES : constants_1.WDA_SIM_STARTUP_RETRIES); const startupRetryInterval = driver.opts.wdaStartupRetryInterval || constants_1.WDA_STARTUP_RETRY_INTERVAL; if ((0, constants_1.isXcodebuildNeeded)(driver.opts)) { driver.log.debug(`Trying to start WebDriverAgent ${startupRetries} times with ${startupRetryInterval}ms interval`); if (!support_1.util.hasValue(driver.opts.wdaStartupRetries) && !support_1.util.hasValue(driver.opts.wdaStartupRetryInterval)) { driver.log.debug(`These values can be customized by changing wdaStartupRetries/wdaStartupRetryInterval capabilities`); } } else { driver.log.debug(`Trying to start WebDriverAgent once since at least one of ${constants_1.CAP_NAMES_NO_XCODEBUILD_REQUIRED} capabilities is provided`); startupRetries = 1; } return { startupRetries, startupRetryInterval }; } async function runStartupWithRetries(driver, startupRetries, startupRetryInterval) { let shortCircuitError = null; let retryCount = 0; await (0, asyncbox_1.retryInterval)(startupRetries, startupRetryInterval, async () => { driver.logEvent('wdaStartAttempted'); if (retryCount > 0) { driver.log.info(`Retrying WDA startup (${retryCount + 1} of ${startupRetries})`); } try { await launchOnce(driver); } catch (err) { retryCount++; await handleLaunchFailure(driver, err); } shortCircuitError = await establishProxySession(driver); if (shortCircuitError) { return; } await finalizeSuccessfulStartup(driver); }); if (shortCircuitError) { throw shortCircuitError; } } async function launchOnce(driver) { if (driver.opts.usePreinstalledWDA) { await preparePreinstalled(driver); } if (!driver.sessionId) { throw new Error('Session ID is required but was not set'); } driver.cachedWdaStatus = await driver.wda.launch(driver.sessionId); } async function handleLaunchFailure(driver, err) { driver.logEvent('wdaStartFailed'); const cause = err instanceof Error ? err : new Error(String(err)); driver.log.debug(cause.stack); let errorMsg = `Unable to launch WebDriverAgent. Original error: ${cause.message}`; if (driver.isRealDevice()) { errorMsg += `. Make sure you follow the tutorial at ${constants_1.WDA_REAL_DEV_TUTORIAL_URL}`; } if (driver.opts.usePreinstalledWDA) { try { await driver.wda.quit(); } catch { } errorMsg = `Unable to launch WebDriverAgent. Original error: ${cause.message}. ` + `Make sure the application ${driver.wda.bundleIdForXctest} exists and it is launchable.`; if (driver.isRealDevice()) { errorMsg += ` ${constants_1.WDA_REAL_DEV_TUTORIAL_URL} may help to complete the preparation.`; } throw new Error(errorMsg, { cause: err }); } await quitAndThrow(driver, errorMsg); } async function establishProxySession(driver) { driver.proxyReqRes = driver.wda.proxyReqRes.bind(driver.wda); driver.jwpProxyActive = true; try { driver.logEvent('wdaSessionAttempted'); driver.log.debug('Sending createSession command to WDA'); driver.cachedWdaStatus = driver.cachedWdaStatus || (await driver.proxyCommand('/status', 'GET')); await createWdaSession(driver, driver.opts.bundleId, driver.opts.processArguments); driver.logEvent('wdaSessionStarted'); return null; } catch (err) { driver.logEvent('wdaSessionFailed'); if (err instanceof driver_1.errors.TimeoutError) { driver.log.debug(err.stack); return err; } const cause = err instanceof Error ? err : new Error(String(err)); driver.log.debug(cause.stack); let errorMsg = `Unable to start WebDriverAgent session. Original error: ${cause.message}`; if (driver.isRealDevice() && cause.message?.includes('xcodebuild')) { errorMsg += ` Make sure you follow the tutorial at ${constants_1.WDA_REAL_DEV_TUTORIAL_URL}.`; } throw new Error(errorMsg, { cause: err }); } } async function finalizeSuccessfulStartup(driver) { if (driver.opts.clearSystemFiles && (0, constants_1.isXcodebuildNeeded)(driver.opts)) { await (0, cleanup_1.markSystemFilesForCleanup)(() => driver.wda.retrieveDerivedDataPath()); } if (driver.cachedWdaStatus?.build) { driver.log.info(`WebDriverAgent version: '${driver.cachedWdaStatus.build.version}'`); } else { driver.log.warn(`WebDriverAgent does not provide any version information. ` + `This might indicate either a custom or an outdated build.`); } driver.wda.fullyStarted = true; driver.logEvent('wdaStarted'); } async function createWdaSession(driver, bundleId, processArguments) { const args = processArguments ? structuredClone(processArguments.args) || [] : []; if (!Array.isArray(args)) { throw new Error(`processArguments.args capability is expected to be an array. ` + `${JSON.stringify(args)} is given instead`); } const env = processArguments ? structuredClone(processArguments.env) || {} : {}; if (!(0, utils_1.isPlainObject)(env)) { throw new Error(`processArguments.env capability is expected to be a dictionary. ` + `${JSON.stringify(env)} is given instead`); } if (support_1.util.hasValue(driver.opts.language)) { args.push('-AppleLanguages', `(${driver.opts.language})`); args.push('-NSLanguages', `(${driver.opts.language})`); } if (support_1.util.hasValue(driver.opts.locale)) { args.push('-AppleLocale', driver.opts.locale); } if (driver.opts.noReset) { if (driver.opts.shouldTerminateApp == null) { driver.opts.shouldTerminateApp = false; } if (driver.opts.forceAppLaunch == null) { driver.opts.forceAppLaunch = false; } } if (support_1.util.hasValue(driver.opts.appTimeZone)) { // https://developer.apple.com/forums/thread/86951?answerId=263395022#263395022 env.TZ = driver.opts.appTimeZone; } const wdaCaps = { bundleId: driver.opts.autoLaunch === false ? undefined : bundleId, arguments: args, environment: env, eventloopIdleDelaySec: driver.opts.wdaEventloopIdleDelay ?? 0, shouldWaitForQuiescence: true, maxTypingFrequency: driver.opts.maxTypingFrequency ?? 60, shouldUseSingletonTestManager: driver.opts.shouldUseSingletonTestManager ?? true, waitForIdleTimeout: driver.opts.waitForIdleTimeout, shouldUseCompactResponses: driver.opts.shouldUseCompactResponses, elementResponseFields: driver.opts.elementResponseFields, disableAutomaticScreenshots: driver.opts.disableAutomaticScreenshots, shouldTerminateApp: driver.opts.shouldTerminateApp ?? true, forceAppLaunch: driver.opts.forceAppLaunch ?? true, appLaunchStateTimeoutSec: driver.opts.appLaunchStateTimeoutSec, useNativeCachingStrategy: driver.opts.useNativeCachingStrategy ?? true, forceSimulatorSoftwareKeyboardPresence: driver.opts.forceSimulatorSoftwareKeyboardPresence ?? (driver.opts.connectHardwareKeyboard === true ? false : true), }; if (driver.opts.autoAcceptAlerts) { wdaCaps.defaultAlertAction = 'accept'; } else if (driver.opts.autoDismissAlerts) { wdaCaps.defaultAlertAction = 'dismiss'; } if (driver.opts.initialDeeplinkUrl) { driver.log.info(`The deeplink URL will be set to ${driver.opts.initialDeeplinkUrl}`); wdaCaps.initialUrl = driver.opts.initialDeeplinkUrl; } const timer = new support_1.timing.Timer().start(); await driver.proxyCommand('/session', 'POST', { capabilities: { firstMatch: [wdaCaps], alwaysMatch: {}, }, }); driver.log.info(`WDA session startup took ${timer.getDuration().asMilliSeconds.toFixed(0)}ms`); } async function quitAndThrow(driver, msg) { driver.log.debug(msg); if (!(0, constants_1.isXcodebuildNeeded)(driver.opts)) { driver.log.debug(`Not quitting WebDriverAgent since at least one of ${constants_1.CAP_NAMES_NO_XCODEBUILD_REQUIRED} capabilities is provided`); throw new Error(msg); } driver.log.info('Quitting WebDriverAgent'); await driver.wda.quit(); throw new Error(msg); } //# sourceMappingURL=startup.js.map