diff --git a/lib/commands/condition.js b/lib/commands/condition.js index 124985f4a..fc09488e6 100644 --- a/lib/commands/condition.js +++ b/lib/commands/condition.js @@ -1,13 +1,13 @@ import {INSTRUMENT_CHANNEL, services} from 'appium-ios-device'; import _ from 'lodash'; -import {util} from 'appium/support'; -function requireConditionInducerCompatibleDevice(driver) { - if (driver.isSimulator()) { - driver.log.errorAndThrow('Condition inducer only works on real devices'); - } - if (util.compareVersions(driver.opts.platformVersion, '<', '13.0')) { - driver.log.errorAndThrow('Condition inducer is only supported since iOS 13+'); +/** + * @this {XCUITestDriver} + * @returns {void} + */ +function requireConditionInducerCompatibleDevice() { + if (this.isSimulator()) { + throw this.log.errorWithException('Condition inducer only works on real devices'); } } @@ -62,7 +62,7 @@ export default { * @this {XCUITestDriver} */ async listConditionInducers() { - requireConditionInducerCompatibleDevice(this); + requireConditionInducerCompatibleDevice.bind(this); const conditionInducerService = await services.startInstrumentService(this.device.udid); try { const ret = await conditionInducerService.callChannel( @@ -95,9 +95,11 @@ export default { * @this {XCUITestDriver} */ async enableConditionInducer(conditionID, profileID) { - requireConditionInducerCompatibleDevice(this); + requireConditionInducerCompatibleDevice.bind(this); if (this._conditionInducerService && !this._conditionInducerService._socketClient.destroyed) { - this.log.errorAndThrow(`Condition inducer has been started. A condition is already active.`); + throw this.log.errorWithException( + `Condition inducer has been started. A condition is already active.` + ); } this._conditionInducerService = await services.startInstrumentService(this.device.udid); const ret = await this._conditionInducerService.callChannel( @@ -109,7 +111,7 @@ export default { if (!_.isBoolean(ret.selector)) { this._conditionInducerService.close(); this._conditionInducerService = null; - this.log.errorAndThrow(`Enable condition inducer error: '${JSON.stringify(ret.selector)}'`); + throw this.log.errorWithException(`Enable condition inducer error: '${JSON.stringify(ret.selector)}'`); } return ret.selector; }, diff --git a/lib/commands/file-movement.js b/lib/commands/file-movement.js index 0fb0b1606..23725ce8c 100644 --- a/lib/commands/file-movement.js +++ b/lib/commands/file-movement.js @@ -1,7 +1,6 @@ import _ from 'lodash'; import {fs, tempDir, mkdirp, zip, util} from 'appium/support'; import path from 'path'; -import log from '../logger'; import {services} from 'appium-ios-device'; import {pullFile, pullFolder, pushFile} from '../ios-fs-helpers'; import {errors} from 'appium/driver'; @@ -16,6 +15,7 @@ const OBJECT_NOT_FOUND_ERROR_MESSAGE = 'OBJECT_NOT_FOUND'; /** * Parses the actual path and the bundle identifier from the given path string * + * @this {XCUITestDriver} * @param {string} remotePath - The given path string. The string should * match `CONTAINER_PATH_PATTERN` regexp, otherwise an error is going * to be thrown. A valid string example: `@bundle.identifier:container_type/relative_path_in_container` @@ -38,7 +38,7 @@ export async function parseContainerPath(remotePath, containerRootSupplier) { // not counting the colon if (typeSeparatorPos > 0 && typeSeparatorPos < bundleId.length - 1) { containerType = bundleId.substring(typeSeparatorPos + 1); - log.debug(`Parsed container type: ${containerType}`); + this.log.debug(`Parsed container type: ${containerType}`); bundleId = bundleId.substring(0, typeSeparatorPos); } if (_.isNil(containerRootSupplier)) { @@ -53,6 +53,12 @@ export async function parseContainerPath(remotePath, containerRootSupplier) { return {bundleId, pathInContainer, containerType}; } +/** + * + * @param {string} originalPath + * @param {string} root + * @returns {void} + */ function verifyIsSubPath(originalPath, root) { const normalizedRoot = path.normalize(root); const normalizedPath = path.normalize(path.dirname(originalPath)); @@ -62,6 +68,13 @@ function verifyIsSubPath(originalPath, root) { } } +/** + * + * @param {string} udid + * @param {string} [bundleId] + * @param {string} [containerType] + * @returns {Promise} + */ async function createAfcClient(udid, bundleId, containerType) { if (!bundleId) { return await services.startAfcService(udid); @@ -72,20 +85,31 @@ async function createAfcClient(udid, bundleId, containerType) { : await service.vendContainer(bundleId); } +/** + * + * @param {string} [containerType] + * @returns {boolean} + */ function isDocumentsContainer(containerType) { return _.toLower(containerType) === _.toLower(CONTAINER_DOCUMENTS_PATH); } -async function createService(udid, remotePath) { +/** + * + * @this {XCUITestDriver} + * @param {string} remotePath + * @returns {Promise<{service: any, relativePath: string}>} + */ +async function createService(remotePath) { if (CONTAINER_PATH_PATTERN.test(remotePath)) { - const {bundleId, pathInContainer, containerType} = await parseContainerPath(remotePath); - const service = await createAfcClient(udid, bundleId, containerType); + const {bundleId, pathInContainer, containerType} = await parseContainerPath.bind(this)(remotePath); + const service = await createAfcClient(this.device.udid, bundleId, containerType); const relativePath = isDocumentsContainer(containerType) ? path.join(CONTAINER_DOCUMENTS_PATH, pathInContainer) : pathInContainer; return {service, relativePath}; } else { - const service = await createAfcClient(udid); + const service = await createAfcClient(this.device.udid); return {service, relativePath: remotePath}; } } @@ -93,9 +117,7 @@ async function createService(udid, remotePath) { /** * Save the given base64 data chunk as a binary file on the Simulator under test. * - * @param {import('../driver').Simulator} device - The device object, which represents the device under test. - * This object is expected to have the `udid` property containing the - * valid device ID. + * @this {XCUITestDriver} * @param {string} remotePath - The remote path on the device. This variable can be prefixed with * bundle id, so then the file will be uploaded to the corresponding * application container instead of the default media folder, for example @@ -108,20 +130,21 @@ async function createService(udid, remotePath) { * to the default media folder and only the file name is considered important. * @param {string} base64Data - Base-64 encoded content of the file to be uploaded. */ -async function pushFileToSimulator(device, remotePath, base64Data) { +async function pushFileToSimulator(remotePath, base64Data) { const buffer = Buffer.from(base64Data, 'base64'); + const device = /** @type {import('../driver').Simulator} */ (this.device); if (CONTAINER_PATH_PATTERN.test(remotePath)) { - const {bundleId, pathInContainer: dstPath} = await parseContainerPath( + const {bundleId, pathInContainer: dstPath} = await parseContainerPath.bind(this)( remotePath, async (appBundle, containerType) => await device.simctl.getAppContainer(appBundle, containerType), ); - log.info( + this.log.info( `Parsed bundle identifier '${bundleId}' from '${remotePath}'. ` + `Will put the data into '${dstPath}'`, ); if (!(await fs.exists(path.dirname(dstPath)))) { - log.debug(`The destination folder '${path.dirname(dstPath)}' does not exist. Creating...`); + this.log.debug(`The destination folder '${path.dirname(dstPath)}' does not exist. Creating...`); await mkdirp(path.dirname(dstPath)); } await fs.writeFile(dstPath, buffer); @@ -140,9 +163,7 @@ async function pushFileToSimulator(device, remotePath, base64Data) { /** * Save the given base64 data chunk as a binary file on the device under test. * - * @param {import('../real-device').RealDevice} device - The device object, which represents the device under test. - * This object is expected to have the `udid` property containing the - * valid device ID. + * @this {XCUITestDriver} * @param {string} remotePath - The remote path on the device. This variable can be prefixed with * bundle id, so then the file will be uploaded to the corresponding * application container instead of the default media folder. Use @@ -157,31 +178,35 @@ async function pushFileToSimulator(device, remotePath, base64Data) { * as base64 decoded data. * @param {string} base64Data - Base-64 encoded content of the file to be uploaded. */ -async function pushFileToRealDevice(device, remotePath, base64Data) { - const {service, relativePath} = await createService(device.udid, remotePath); +async function pushFileToRealDevice(remotePath, base64Data) { + const {service, relativePath} = await createService.bind(this)(remotePath); try { await pushFile(service, Buffer.from(base64Data, 'base64'), relativePath); } catch (e) { - log.debug(e.stack); - throw new Error(`Could not push the file to '${remotePath}'. Original error: ${e.message}`); + this.log.debug(e.stack); + throw new Error(`Could not push the file to '${remotePath}'. Original error: ${e.message}`); } finally { service.close(); } } -async function deleteFileOrFolder(device, remotePath, isSimulator) { - return isSimulator - ? await deleteFromSimulator(device, remotePath) - : await deleteFromRealDevice(device, remotePath); +/** + * + * @this {XCUITestDriver} + * @param {string} remotePath + * @returns {Promise} + */ +async function deleteFileOrFolder(remotePath) { + return this.isSimulator() + ? await deleteFromSimulator.bind(this)(remotePath) + : await deleteFromRealDevice.bind(this)(remotePath); } /** * Get the content of given file or folder from iOS Simulator and return it as base-64 encoded string. * Folder content is recursively packed into a zip archive. * - * @param {import('../driver').Simulator} device - The device object, which represents the device under test. - * This object is expected to have the `udid` property containing the - * valid device ID. + * @this {XCUITestDriver} * @param {string} remotePath - The path to a file or a folder, which exists in the corresponding application * container on Simulator. Use * `@:/` @@ -191,15 +216,16 @@ async function deleteFileOrFolder(device, remotePath, isSimulator) { * @param {boolean} isFile - Whether the destination item is a file or a folder * @returns {Promise} Base-64 encoded content of the file. */ -async function pullFromSimulator(device, remotePath, isFile) { +async function pullFromSimulator(remotePath, isFile) { let pathOnServer; + const device = /** @type {import('../driver').Simulator} */ (this.device); if (CONTAINER_PATH_PATTERN.test(remotePath)) { - const {bundleId, pathInContainer: dstPath} = await parseContainerPath( + const {bundleId, pathInContainer: dstPath} = await parseContainerPath.bind(this)( remotePath, async (appBundle, containerType) => await device.simctl.getAppContainer(appBundle, containerType), ); - log.info( + this.log.info( `Parsed bundle identifier '${bundleId}' from '${remotePath}'. ` + `Will get the data from '${dstPath}'`, ); @@ -208,10 +234,10 @@ async function pullFromSimulator(device, remotePath, isFile) { const simRoot = device.getDir(); pathOnServer = path.posix.join(simRoot, remotePath); verifyIsSubPath(pathOnServer, simRoot); - log.info(`Got the full item path: ${pathOnServer}`); + this.log.info(`Got the full item path: ${pathOnServer}`); } if (!(await fs.exists(pathOnServer))) { - log.errorAndThrow( + throw this.log.errorWithException( `The remote ${isFile ? 'file' : 'folder'} at '${pathOnServer}' does not exist`, ); } @@ -225,9 +251,7 @@ async function pullFromSimulator(device, remotePath, isFile) { * Get the content of given file or folder from the real device under test and return it as base-64 encoded string. * Folder content is recursively packed into a zip archive. * - * @param {import('../real-device').RealDevice} device - The device object, which represents the device under test. - * This object is expected to have the `udid` property containing the - * valid device ID. + * @this {XCUITestDriver} * @param {string} remotePath - The path to an existing remote file on the device. This variable can be prefixed with * bundle id, so then the file will be downloaded from the corresponding * application container instead of the default media folder. Use @@ -244,8 +268,8 @@ async function pullFromSimulator(device, remotePath, isFile) { * @param {boolean} isFile - Whether the destination item is a file or a folder * @returns {Promise} Base-64 encoded content of the remote file */ -async function pullFromRealDevice(device, remotePath, isFile) { - const {service, relativePath} = await createService(device.udid, remotePath); +async function pullFromRealDevice(remotePath, isFile) { + const {service, relativePath} = await createService.bind(this)(remotePath); try { const fileInfo = await service.getFileInfo(relativePath); if (isFile && fileInfo.isDirectory()) { @@ -266,25 +290,25 @@ async function pullFromRealDevice(device, remotePath, isFile) { /** * Remove the file or folder from the device * - * @param {import('../driver').Simulator} device - The device object, which represents the device under test. - * This object is expected to have the `udid` property containing the - * valid device ID. + * @this {XCUITestDriver} * @param {string} remotePath - The path to a file or a folder, which exists in the corresponding application * container on Simulator. Use * `@:/` * format to pull a file or a folder from an application container of the given type. * Possible container types are 'app', 'data', 'groups', ''. * The default type is 'app'. + * @returns {Promise} */ -async function deleteFromSimulator(device, remotePath) { +async function deleteFromSimulator(remotePath) { let pathOnServer; + const device = /** @type {import('../driver').Simulator} */ (this.device); if (CONTAINER_PATH_PATTERN.test(remotePath)) { - const {bundleId, pathInContainer: dstPath} = await parseContainerPath( + const {bundleId, pathInContainer: dstPath} = await parseContainerPath.bind(this)( remotePath, async (appBundle, containerType) => await device.simctl.getAppContainer(appBundle, containerType), ); - log.info( + this.log.info( `Parsed bundle identifier '${bundleId}' from '${remotePath}'. ` + `'${dstPath}' will be deleted`, ); @@ -293,7 +317,7 @@ async function deleteFromSimulator(device, remotePath) { const simRoot = device.getDir(); pathOnServer = path.posix.join(simRoot, remotePath); verifyIsSubPath(pathOnServer, simRoot); - log.info(`Got the full path: ${pathOnServer}`); + this.log.info(`Got the full path: ${pathOnServer}`); } if (!(await fs.exists(pathOnServer))) { throw new errors.InvalidArgumentError(`The remote path at '${pathOnServer}' does not exist`); @@ -304,9 +328,7 @@ async function deleteFromSimulator(device, remotePath) { /** * Remove the file or folder from the device * - * @param {import('../real-device').RealDevice} device - The device object, which represents the device under test. - * This object is expected to have the `udid` property containing the - * valid device ID. + * @this {XCUITestDriver} * @param {string} remotePath - The path to an existing remote file on the device. This variable can be prefixed with * bundle id, so then the file will be downloaded from the corresponding * application container instead of the default media folder. Use @@ -320,9 +342,10 @@ async function deleteFromSimulator(device, remotePath) { * `On My iPhone//111.png` wil be pulled into the mounted host machine * and Appium returns the data as base64-encoded string to client. * `@com.myapp.bla:documents/` means `On My iPhone/`. + * @returns {Promise} */ -async function deleteFromRealDevice(device, remotePath) { - const {service, relativePath} = await createService(device.udid, remotePath); +async function deleteFromRealDevice(remotePath) { + const {service, relativePath} = await createService.bind(this)(remotePath); try { await service.deleteDirectory(relativePath); } catch (e) { @@ -361,8 +384,8 @@ export default { base64Data = Buffer.from(base64Data).toString('utf8'); } return this.isSimulator() - ? await pushFileToSimulator(/** @type {import('../driver').Simulator} */ (this.device), remotePath, base64Data) - : await pushFileToRealDevice(/** @type {import('../real-device').RealDevice} */ (this.device), remotePath, base64Data); + ? await pushFileToSimulator.bind(this)(remotePath, base64Data) + : await pushFileToRealDevice.bind(this)(remotePath, base64Data); }, /** @@ -396,8 +419,8 @@ export default { ); } return this.isSimulator() - ? await pullFromSimulator(/** @type {import('../driver').Simulator} */ (this.device), remotePath, true) - : await pullFromRealDevice(/** @type {import('../real-device').RealDevice} */ (this.device), remotePath, true); + ? await pullFromSimulator.bind(this)(remotePath, true) + : await pullFromRealDevice.bind(this)(remotePath, true); }, /** @@ -423,7 +446,7 @@ export default { if (!remotePath.endsWith('/')) { remotePath = `${remotePath}/`; } - await deleteFileOrFolder(this.device, remotePath, this.isSimulator()); + await deleteFileOrFolder.bind(this)(remotePath); }, /** @@ -440,7 +463,7 @@ export default { `'${remotePath}' is given instead`, ); } - await deleteFileOrFolder(this.device, remotePath, this.isSimulator()); + await deleteFileOrFolder.bind(this)(remotePath); }, /** @@ -457,8 +480,8 @@ export default { remotePath = `${remotePath}/`; } return this.isSimulator() - ? await pullFromSimulator(/** @type {import('../driver').Simulator} */ (this.device), remotePath, false) - : await pullFromRealDevice(/** @type {import('../real-device').RealDevice} */ (this.device), remotePath, false); + ? await pullFromSimulator.bind(this)(remotePath, false) + : await pullFromRealDevice.bind(this)(remotePath, false); }, /** diff --git a/lib/commands/pcap.js b/lib/commands/pcap.js index 8b156d2e5..c6fc14a85 100644 --- a/lib/commands/pcap.js +++ b/lib/commands/pcap.js @@ -83,7 +83,7 @@ export default { */ async mobileStartPcap(timeLimitSec = 180, forceRestart = false) { if (this.isSimulator()) { - this.log.errorAndThrow('Network traffic capture only works on real devices'); + throw this.log.errorWithException('Network traffic capture only works on real devices'); } if (this._trafficCapture?.isCapturing()) { @@ -150,7 +150,7 @@ export default { try { resultPath = await this._trafficCapture.finish(); if (!(await fs.exists(resultPath))) { - this.log.errorAndThrow( + throw this.log.errorWithException( `The network traffic capture utility has failed ` + `to store the actual traffic capture at '${resultPath}'`, ); diff --git a/lib/commands/performance.js b/lib/commands/performance.js index 59cda3dc0..cdf113335 100644 --- a/lib/commands/performance.js +++ b/lib/commands/performance.js @@ -223,7 +223,7 @@ export class PerfRecorder { await this._enforceTermination(); const listProfilesCommand = toolName === XCTRACE ? `${XCRUN} ${XCTRACE} list templates` : `${INSTRUMENTS} -s`; - this._logger.errorAndThrow( + throw this._logger.errorWithException( `There is no ${DEFAULT_EXT} file found for performance profile ` + `'${this._profileName}'. Make sure the profile is supported on this device. ` + `You could use '${listProfilesCommand}' command to see the list of all available profiles. ` + @@ -246,7 +246,7 @@ export class PerfRecorder { try { await this._process?.stop('SIGINT', STOP_TIMEOUT_MS); } catch (e) { - this._logger.errorAndThrow( + throw this._logger.errorWithException( `Performance recording has failed to exit after ${STOP_TIMEOUT_MS}ms`, ); } @@ -279,7 +279,7 @@ export default { pid, ) { if (!this.isFeatureEnabled(PERF_RECORD_FEAT_NAME) && !this.isRealDevice()) { - this.log.errorAndThrow(PERF_RECORD_SECURITY_MESSAGE); + throw this.log.errorWithException(PERF_RECORD_SECURITY_MESSAGE); } if (!_.isEmpty(this._perfRecorders)) { @@ -348,7 +348,7 @@ export default { formFields, ) { if (!this.isFeatureEnabled(PERF_RECORD_FEAT_NAME) && !this.isRealDevice()) { - this.log.errorAndThrow(PERF_RECORD_SECURITY_MESSAGE); + throw this.log.errorWithException(PERF_RECORD_SECURITY_MESSAGE); } if (_.isEmpty(this._perfRecorders)) { @@ -358,7 +358,7 @@ export default { const recorders = this._perfRecorders.filter((x) => x.profileName === profileName); if (_.isEmpty(recorders)) { - this.log.errorAndThrow( + throw this.log.errorWithException( `There are no records for performance profile '${profileName}' ` + `and device ${this.device.udid}. Have you started the profiling before?`, ); @@ -367,7 +367,7 @@ export default { const recorder = _.first(recorders); const resultPath = await /** @type {PerfRecorder} */ (recorder).stop(); if (!(await fs.exists(resultPath))) { - this.log.errorAndThrow( + throw this.log.errorWithException( `There is no ${DEFAULT_EXT} file found for performance profile '${profileName}' ` + `and device ${this.device.udid}. Make sure the selected profile is supported on this device`, ); diff --git a/lib/commands/proxy-helper.js b/lib/commands/proxy-helper.js index 015c6813d..9aa0288e0 100644 --- a/lib/commands/proxy-helper.js +++ b/lib/commands/proxy-helper.js @@ -76,9 +76,9 @@ export default { } if (!url) { - this.log.errorAndThrow('Proxying requires an endpoint'); + throw this.log.errorWithException('Proxying requires an endpoint'); } else if (!SUPPORTED_METHODS.has(method)) { - this.log.errorAndThrow( + throw this.log.errorWithException( `Proxying only works for the following HTTP methods: ${[...SUPPORTED_METHODS].join(', ')}`, ); } diff --git a/lib/commands/record-audio.js b/lib/commands/record-audio.js index a6e523232..561c6557a 100644 --- a/lib/commands/record-audio.js +++ b/lib/commands/record-audio.js @@ -166,14 +166,14 @@ export default { forceRestart = false, ) { if (!this.isFeatureEnabled(AUDIO_RECORD_FEAT_NAME)) { - this.log.errorAndThrow( + throw this.log.errorWithException( `Audio capture feature must be enabled on the server side. ` + `Please set '--relaxed-security' or '--allow-insecure' with '${AUDIO_RECORD_FEAT_NAME}' option. ` + `Read https://github.com/appium/appium/blob/master/docs/en/writing-running-appium/security.md for more details.`, ); } if (!audioInput) { - this.log.errorAndThrow( + throw this.log.errorWithException( `The mandatory audioInput option is not provided. Please set it ` + `to a correct value (e. g. ':1'). Use 'ffmpeg -f avfoundation -list_devices true -i ""' ` + `command to list available input sources`, @@ -213,7 +213,7 @@ export default { const timeoutSeconds = parseInt(String(timeLimit), 10); if (isNaN(timeoutSeconds) || timeoutSeconds > MAX_RECORDING_TIME_SEC || timeoutSeconds <= 0) { - this.log.errorAndThrow( + throw this.log.errorWithException( `The timeLimit value must be in range [1, ${MAX_RECORDING_TIME_SEC}] seconds. ` + `The value of '${timeLimit}' has been passed instead.`, ); @@ -250,7 +250,7 @@ export default { try { resultPath = await this._audioRecorder.finish(); if (!(await fs.exists(resultPath))) { - this.log.errorAndThrow( + throw this.log.errorWithException( `${FFMPEG_BINARY} has failed ` + `to store the actual audio recording at '${resultPath}'`, ); } diff --git a/lib/commands/recordscreen.js b/lib/commands/recordscreen.js index f70761160..569c1631d 100644 --- a/lib/commands/recordscreen.js +++ b/lib/commands/recordscreen.js @@ -240,7 +240,7 @@ export default { pixelFormat, }); if (!(await screenRecorder.interrupt(true))) { - this.log.errorAndThrow('Unable to stop screen recording process'); + throw this.log.errorWithException('Unable to stop screen recording process'); } if (this._recentScreenRecorder) { await this._recentScreenRecorder.cleanup(); @@ -249,7 +249,7 @@ export default { const timeoutSeconds = parseFloat(String(timeLimit)); if (isNaN(timeoutSeconds) || timeoutSeconds > MAX_RECORDING_TIME_SEC || timeoutSeconds <= 0) { - this.log.errorAndThrow( + throw this.log.errorWithException( `The timeLimit value must be in range [1, ${MAX_RECORDING_TIME_SEC}] seconds. ` + `The value of '${timeLimit}' has been passed instead.`, ); @@ -333,7 +333,7 @@ export default { try { const videoPath = await this._recentScreenRecorder.finish(); if (!(await fs.exists(videoPath))) { - this.log.errorAndThrow( + throw this.log.errorWithException( `The screen recorder utility has failed ` + `to store the actual screen recording at '${videoPath}'`, ); diff --git a/lib/driver.js b/lib/driver.js index 563f41946..b1a632771 100644 --- a/lib/driver.js +++ b/lib/driver.js @@ -628,7 +628,7 @@ export class XCUITestDriver extends BaseDriver { !this.isSafari() && !(await this.device.isAppInstalled(this.opts.bundleId)) ) { - this.log.errorAndThrow(`App with bundle identifier '${this.opts.bundleId}' unknown`); + throw this.log.errorWithException(`App with bundle identifier '${this.opts.bundleId}' unknown`); } if (this.isSimulator()) { @@ -1385,10 +1385,10 @@ export class XCUITestDriver extends BaseDriver { let verifyProcessArgument = (processArguments) => { const {args, env} = processArguments; if (!_.isNil(args) && !_.isArray(args)) { - this.log.errorAndThrow('processArguments.args must be an array of strings'); + throw this.log.errorWithException('processArguments.args must be an array of strings'); } if (!_.isNil(env) && !_.isPlainObject(env)) { - this.log.errorAndThrow( + throw this.log.errorWithException( 'processArguments.env must be an object pair {a:b, c:d}', ); } @@ -1402,7 +1402,7 @@ export class XCUITestDriver extends BaseDriver { caps.processArguments = JSON.parse(caps.processArguments); verifyProcessArgument(caps.processArguments); } catch (err) { - this.log.errorAndThrow( + throw this.log.errorWithException( `processArguments must be a JSON format or an object with format {args : [], env : {a:b, c:d}}. ` + `Both environment and argument can be null. Error: ${err}`, ); @@ -1410,7 +1410,7 @@ export class XCUITestDriver extends BaseDriver { } else if (_.isPlainObject(caps.processArguments)) { verifyProcessArgument(caps.processArguments); } else { - this.log.errorAndThrow( + throw this.log.errorWithException( `'processArguments must be an object, or a string JSON object with format {args : [], env : {a:b, c:d}}. ` + `Both environment and argument can be null.`, ); @@ -1422,7 +1422,7 @@ export class XCUITestDriver extends BaseDriver { (caps.keychainPath && !caps.keychainPassword) || (!caps.keychainPath && caps.keychainPassword) ) { - this.log.errorAndThrow( + throw this.log.errorWithException( `If 'keychainPath' is set, 'keychainPassword' must also be set (and vice versa).`, ); } @@ -1439,7 +1439,7 @@ export class XCUITestDriver extends BaseDriver { if (_.isString(caps.webDriverAgentUrl)) { const {protocol, host} = url.parse(caps.webDriverAgentUrl); if (_.isEmpty(protocol) || _.isEmpty(host)) { - this.log.errorAndThrow( + throw this.log.errorWithException( `'webDriverAgentUrl' capability is expected to contain a valid WebDriverAgent server URL. ` + `'${caps.webDriverAgentUrl}' is given instead`, ); @@ -1448,7 +1448,9 @@ export class XCUITestDriver extends BaseDriver { if (caps.browserName) { if (caps.bundleId) { - this.log.errorAndThrow(`'browserName' cannot be set together with 'bundleId' capability`); + throw this.log.errorWithException( + `'browserName' cannot be set together with 'bundleId' capability` + ); } // warn if the capabilities have both `app` and `browser, although this // is common with selenium grid @@ -1470,7 +1472,7 @@ export class XCUITestDriver extends BaseDriver { } } } catch (e) { - this.log.errorAndThrow( + throw this.log.errorWithException( `'${caps.permissions}' is expected to be a valid object with format ` + `{"": {"": "", ...}, ...}. Original error: ${e.message}`, ); @@ -1478,7 +1480,7 @@ export class XCUITestDriver extends BaseDriver { } if (caps.platformVersion && !util.coerceVersion(caps.platformVersion, false)) { - this.log.errorAndThrow( + throw this.log.errorWithException( `'platformVersion' must be a valid version number. ` + `'${caps.platformVersion}' is given instead.`, ); @@ -1617,7 +1619,7 @@ export class XCUITestDriver extends BaseDriver { try { appsList = this.helpers.parseCapsArray(otherApps); } catch (e) { - this.log.errorAndThrow(`Could not parse "otherApps" capability: ${e.message}`); + throw this.log.errorWithException(`Could not parse "otherApps" capability: ${e.message}`); } if (!appsList || !appsList.length) { this.log.info(`Got zero apps from 'otherApps' capability value. Doing nothing`); diff --git a/lib/utils.js b/lib/utils.js index bc50e8844..7e811d3e2 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -225,7 +225,7 @@ async function clearSystemFiles(wda) { async function checkAppPresent(app) { log.debug(`Checking whether app '${app}' is actually present on file system`); if (!(await fs.exists(app))) { - log.errorAndThrow(`Could not find app at '${app}'`); + throw log.errorWithException(`Could not find app at '${app}'`); } log.debug('App is present'); } @@ -295,13 +295,13 @@ function normalizeCommandTimeouts(value) { throw new Error(); } } catch (err) { - log.errorAndThrow( + throw log.errorWithException( `"commandTimeouts" capability should be a valid JSON object. "${value}" was given instead`, ); } for (let [cmd, timeout] of _.toPairs(result)) { if (!_.isInteger(timeout) || timeout <= 0) { - log.errorAndThrow( + throw log.errorWithException( `The timeout for "${cmd}" should be a valid natural number of milliseconds. "${timeout}" was given instead`, ); } @@ -377,7 +377,7 @@ async function getPIDsListeningOnPort(port, filteringFunc = null) { */ async function encodeBase64OrUpload(localPath, remotePath = null, uploadOptions = {}) { if (!(await fs.exists(localPath))) { - log.errorAndThrow(`The file at '${localPath}' does not exist or is not accessible`); + throw log.errorWithException(`The file at '${localPath}' does not exist or is not accessible`); } if (_.isEmpty(remotePath)) { diff --git a/test/unit/commands/file-movement-specs.js b/test/unit/commands/file-movement-specs.js index 3bd48d3f1..c7dd94a28 100644 --- a/test/unit/commands/file-movement-specs.js +++ b/test/unit/commands/file-movement-specs.js @@ -1,5 +1,6 @@ import {parseContainerPath} from '../../../lib/commands/file-movement'; import {tempDir} from 'appium/support'; +import XCUITestDriver from '../../../lib/driver'; describe('file-movement', function () { @@ -16,9 +17,19 @@ describe('file-movement', function () { }); describe('parseContainerPath', function () { + let driver; + + beforeEach(function () { + driver = new XCUITestDriver(); + }); + + afterEach(function () { + driver = null; + }); + it('should parse with container', async function () { const mntRoot = await tempDir.openDir(); - const {bundleId, pathInContainer, containerType} = await parseContainerPath( + const {bundleId, pathInContainer, containerType} = await parseContainerPath.bind(driver)( '@io.appium.example:app/Documents/file.txt', mntRoot, ); @@ -29,7 +40,7 @@ describe('file-movement', function () { }); it('should parse with container root', async function () { const mntRoot = await tempDir.openDir(); - const {bundleId, pathInContainer, containerType} = await parseContainerPath( + const {bundleId, pathInContainer, containerType} = await parseContainerPath.bind(driver)( '@io.appium.example:documents/', mntRoot, ); @@ -40,7 +51,7 @@ describe('file-movement', function () { }); it('should parse without container', async function () { const mntRoot = await tempDir.openDir(); - const {bundleId, pathInContainer, containerType} = await parseContainerPath( + const {bundleId, pathInContainer, containerType} = await parseContainerPath.bind(driver)( '@io.appium.example/Documents/file.txt', mntRoot, ); @@ -51,7 +62,7 @@ describe('file-movement', function () { }); it('should raise an error if no container path', async function () { const mntRoot = await tempDir.openDir(); - await parseContainerPath('@io.appium.example:documents', mntRoot).should.be.rejected; + await parseContainerPath.bind(driver)('@io.appium.example:documents', mntRoot).should.be.rejected; }); }); });