diff --git a/lib/utils.js b/lib/utils.js index 1474d929f..f52ec6fe0 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -265,12 +265,19 @@ function getXctestrunFileName (deviceInfo, version) { : `WebDriverAgentRunner_iphone${deviceInfo.isRealDevice ? `os${version}-arm64` : `simulator${version}-x86_64`}.xctestrun`; } +/** + * Ensures the process is killed after the timeout + * + * @param {string} name + * @param {import('teen_process').SubProcess} proc + * @returns {Promise} + */ async function killProcess (name, proc) { if (!proc || !proc.isRunning) { return; } - log.info(`Shutting down '${name}' process (pid '${proc.proc.pid}')`); + log.info(`Shutting down '${name}' process (pid '${proc.proc?.pid}')`); log.info(`Sending 'SIGTERM'...`); try { diff --git a/lib/xcodebuild.js b/lib/xcodebuild.js index 8194d1984..bdd484db2 100644 --- a/lib/xcodebuild.js +++ b/lib/xcodebuild.js @@ -1,17 +1,17 @@ import { retryInterval } from 'asyncbox'; import { SubProcess, exec } from 'teen_process'; -import { fs, logger, timing } from '@appium/support'; +import { logger, timing } from '@appium/support'; import defaultLogger from './logger'; import B from 'bluebird'; import { setRealDeviceSecurity, setXctestrunFile, updateProjectFile, resetProjectFile, killProcess, - getWDAUpgradeTimestamp, isTvOS } from './utils'; + getWDAUpgradeTimestamp, isTvOS +} from './utils'; import _ from 'lodash'; import path from 'path'; import { EOL } from 'os'; import { WDA_RUNNER_BUNDLE_ID } from './constants'; -import readline from 'node:readline'; const DEFAULT_SIGNING_ID = 'iPhone Developer'; @@ -34,6 +34,9 @@ const xcodeLog = logger.getLogger('Xcode'); class XcodeBuild { + /** @type {SubProcess} */ + xcodebuild; + /** * @param {string} xcodeVersion * @param {any} device @@ -85,10 +88,6 @@ class XcodeBuild { this.resultBundlePath = args.resultBundlePath; this.resultBundleVersion = args.resultBundleVersion; - /** @type {string} */ - this._logLocation = ''; - /** @type {string[]} */ - this._wdaErrorMessage = []; this._didBuildFail = false; this._didProcessExit = false; } @@ -168,8 +167,6 @@ class XcodeBuild { this.usePrebuiltWDA = true; await this.start(true); - this.xcodebuild = null; - if (this.prebuildDelay > 0) { // pause a moment await B.delay(this.prebuildDelay); @@ -286,7 +283,6 @@ class XcodeBuild { if (upgradeTimestamp) { env.UPGRADE_TIMESTAMP = upgradeTimestamp; } - this._logLocation = ''; this._didBuildFail = false; const xcodebuild = new SubProcess(cmd, args, { cwd: this.bootstrapPath, @@ -302,14 +298,6 @@ class XcodeBuild { this.log.debug(`${logMsg}. To change this, use 'showXcodeLog' desired capability`); xcodebuild.on('output', (stdout, stderr) => { let out = stdout || stderr; - // we want to pull out the log file that is created, and highlight it - // for diagnostic purposes - if (out.includes('Writing diagnostic log for test session to')) { - // pull out the first line that begins with the path separator - // which *should* be the line indicating the log file generated - this._logLocation = _.first(_.remove(out.trim().split('\n'), (v) => v.startsWith(path.sep))) ?? ''; - xcodeLog.debug(`Log file location for xcodebuild test: ${this._logLocation || 'unknown'}`); - } // if we have an error we want to output the logs // otherwise the failure is inscrutible @@ -326,9 +314,6 @@ class XcodeBuild { if (logXcodeOutput && !ignoreError) { for (const line of out.split(EOL)) { xcodeLog.error(line); - if (line) { - this._wdaErrorMessage.push(line); - } } } }); @@ -338,43 +323,27 @@ class XcodeBuild { async start (buildOnly = false) { this.xcodebuild = await this.createSubProcess(buildOnly); - // Store xcodebuild message - this._wdaErrorMessage = []; // wrap the start procedure in a promise so that we can catch, and report, // any startup errors that are thrown as events return await new B((resolve, reject) => { - // @ts-ignore xcodebuild must be present here - this.xcodebuild.once('exit', async (code, signal) => { + this.xcodebuild.once('exit', (code, signal) => { xcodeLog.error(`xcodebuild exited with code '${code}' and signal '${signal}'`); - this.xcodebuild?.removeAllListeners(); - const xcodeErrorMessage = this._wdaErrorMessage.join('\n'); - this._wdaErrorMessage = []; - // print out the xcodebuild file if users have asked for it - if (this.showXcodeLog && this._logLocation) { - xcodeLog.error(`Contents of xcodebuild log file '${this._logLocation}':`); - try { - const logFile = readline.createInterface({ - input: fs.createReadStream(this._logLocation), - terminal: false - }); - logFile.on('line', (line) => { - xcodeLog.error(line); - }); - await new B((_resolve) => { - logFile.once('close', () => { - logFile.removeAllListeners(); - _resolve(); - }); - }); - } catch (err) { - xcodeLog.error(`Unable to access xcodebuild log file: '${err.message}'`); - } - } + this.xcodebuild.removeAllListeners(); this.didProcessExit = true; if (this._didBuildFail || (!signal && code !== 0)) { - return reject(new Error(`xcodebuild failed with code ${code}\n` + - `xcodebuild error message:\n${xcodeErrorMessage}`)); + let errorMessage = `xcodebuild failed with code ${code}.` + + ` This usually indicates an issue with the local Xcode setup or WebDriverAgent` + + ` project configuration or the driver-to-platform version mismatch.`; + if (!this.showXcodeLog) { + errorMessage += ` Consider setting 'showXcodeLog' capability to true in` + + ` order to check the Appium server log for build-related error messages.`; + } else if (this.realDevice) { + errorMessage += ` Consider checking the WebDriverAgent configuration guide` + + ` for real iOS devices at` + + ` https://github.com/appium/appium-xcuitest-driver/blob/master/docs/real-device-config.md.`; + } + return reject(new Error(errorMessage)); } // in the case of just building, the process will exit and that is our finish if (buildOnly) { @@ -385,7 +354,6 @@ class XcodeBuild { return (async () => { try { const timer = new timing.Timer().start(); - // @ts-ignore this.xcodebuild must be defined await this.xcodebuild.start(true); if (!buildOnly) { let status = await this.waitForStart(timer);