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