diff --git a/lib/check-dependencies.js b/lib/check-dependencies.js index 49903671d..40fd689a1 100644 --- a/lib/check-dependencies.js +++ b/lib/check-dependencies.js @@ -1,9 +1,6 @@ import { fs } from '@appium/support'; -import _ from 'lodash'; import { exec } from 'teen_process'; -import path from 'path'; -import {XcodeBuild} from './xcodebuild'; -import * as xcode from 'appium-xcode'; +import path from 'node:path'; import { WDA_SCHEME, SDK_SIMULATOR, WDA_RUNNER_APP } from './constants'; @@ -29,14 +26,10 @@ export async function checkForDependencies () { /** * - * @param {XcodeBuild} xcodebuild + * @param {import('./xcodebuild').XcodeBuild} xcodebuild * @returns {Promise} */ export async function bundleWDASim (xcodebuild) { - if (xcodebuild && !_.isFunction(xcodebuild.retrieveDerivedDataPath)) { - xcodebuild = new XcodeBuild(/** @type {import('appium-xcode').XcodeVersion} */ (await xcode.getVersion(true)), {}); - } - const derivedDataPath = await xcodebuild.retrieveDerivedDataPath(); if (!derivedDataPath) { throw new Error('Cannot retrieve the path to the Xcode derived data folder'); diff --git a/lib/types.ts b/lib/types.ts index 82c51cb15..ee5cdf996 100644 --- a/lib/types.ts +++ b/lib/types.ts @@ -50,3 +50,77 @@ export interface WDACapabilities { defaultAlertAction?: 'accept' | 'dismiss'; appLaunchStateTimeoutSec?: number; } + +export interface WebDriverAgentArgs { + device: AppleDevice; // Required + platformVersion?: string; + platformName?: string; + iosSdkVersion?: string; + host?: string; + realDevice?: boolean; + wdaBundlePath?: string; + bootstrapPath?: string; + agentPath?: string; + wdaLocalPort?: number; + wdaRemotePort?: number; + wdaBaseUrl?: string; + prebuildWDA?: boolean; + webDriverAgentUrl?: string; + wdaConnectionTimeout?: number; + useXctestrunFile?: boolean; + usePrebuiltWDA?: boolean; + derivedDataPath?: string; + mjpegServerPort?: number; + updatedWDABundleId?: string; + wdaLaunchTimeout?: number; + usePreinstalledWDA?: boolean; + updatedWDABundleIdSuffix?: string; + showXcodeLog?: boolean; + xcodeConfigFile?: string; + xcodeOrgId?: string; + xcodeSigningId?: string; + keychainPath?: string; + keychainPassword?: string; + useSimpleBuildTest?: boolean; + allowProvisioningDeviceRegistration?: boolean; + resultBundlePath?: string; + resultBundleVersion?: string; + reqBasePath?: string; + launchTimeout?: number; +} + +export interface AppleDevice { + udid: string; + simctl?: any; + devicectl?: any; + idb?: any; + [key: string]: any; +} + +export interface XcodeBuildArgs { + realDevice: boolean; // Required + agentPath: string; // Required + bootstrapPath: string; // Required + platformVersion?: string; + platformName?: string; + iosSdkVersion?: string; + showXcodeLog?: boolean; + xcodeConfigFile?: string; + xcodeOrgId?: string; + xcodeSigningId?: string; + keychainPath?: string; + keychainPassword?: string; + prebuildWDA?: boolean; + usePrebuiltWDA?: boolean; + useSimpleBuildTest?: boolean; + useXctestrunFile?: boolean; + launchTimeout?: number; + wdaRemotePort?: number; + updatedWDABundleId?: string; + derivedDataPath?: string; + mjpegServerPort?: number; + prebuildDelay?: number; + allowProvisioningDeviceRegistration?: boolean; + resultBundlePath?: string; + resultBundleVersion?: string; +} diff --git a/lib/utils.js b/lib/utils.js index 77ca8f53a..4ff845abd 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -161,7 +161,7 @@ async function setRealDeviceSecurity (keychainPath, keychainPassword) { /** * Information of the device under test * @typedef {Object} DeviceInfo - * @property {string} isRealDevice - Equals to true if the current device is a real device + * @property {boolean} isRealDevice - Equals to true if the current device is a real device * @property {string} udid - The device UDID. * @property {string} platformVersion - The platform version of OS. * @property {string} platformName - The platform name of iOS, tvOS diff --git a/lib/webdriveragent.js b/lib/webdriveragent.js index 6d9ab3fd6..7e3f88f07 100644 --- a/lib/webdriveragent.js +++ b/lib/webdriveragent.js @@ -36,11 +36,10 @@ export class WebDriverAgent { /** * @param {import('appium-xcode').XcodeVersion} xcodeVersion - * // TODO: make args typed - * @param {import('@appium/types').StringRecord} [args={}] + * @param {import('./types').WebDriverAgentArgs} args * @param {import('@appium/types').AppiumLogger?} [log=null] */ - constructor (xcodeVersion, args = {}, log = null) { + constructor (xcodeVersion, args, log = null) { this.xcodeVersion = xcodeVersion; this.args = _.clone(args); @@ -52,7 +51,7 @@ export class WebDriverAgent { this.iosSdkVersion = args.iosSdkVersion; this.host = args.host; this.isRealDevice = !!args.realDevice; - this.idb = (args.device || {}).idb; + this.idb = args.device.idb; this.wdaBundlePath = args.wdaBundlePath; this.setWDAPaths(args.bootstrapPath, args.agentPath); @@ -121,7 +120,7 @@ export class WebDriverAgent { get canSkipXcodebuild () { // Use this.args.webDriverAgentUrl to guarantee // the capabilities set gave the `appium:webDriverAgentUrl`. - return this.usePreinstalledWDA || this.args.webDriverAgentUrl; + return this.usePreinstalledWDA || !!this.args.webDriverAgentUrl; } /** @@ -396,7 +395,7 @@ export class WebDriverAgent { // Current method to launch WDA process can be done via 'xcrun devicectl', // but it has limitation about the WDA preinstalled package. // https://github.com/appium/appium/issues/19206#issuecomment-2014182674 - if (util.compareVersions(this.platformVersion, '>=', '17.0')) { + if (this.platformVersion && util.compareVersions(this.platformVersion, '>=', '17.0')) { await this._launchViaDevicectl({env: xctestEnv}); } else { this.xctestApiClient = new Xctest(this.device.udid, this.bundleIdForXctest, null, {env: xctestEnv}); @@ -631,7 +630,7 @@ export class WebDriverAgent { if (!this.args.webDriverAgentUrl) { // if we populated the url ourselves (during `setupCaching` call, for instance) // then clean that up. If the url was supplied, we want to keep it - this.webDriverAgentUrl = null; + this.webDriverAgentUrl = undefined; } } diff --git a/lib/xcodebuild.js b/lib/xcodebuild.js index 041d24ebe..8a25361d0 100644 --- a/lib/xcodebuild.js +++ b/lib/xcodebuild.js @@ -47,12 +47,11 @@ export class XcodeBuild { /** * @param {import('appium-xcode').XcodeVersion} xcodeVersion - * @param {any} device - * // TODO: make args typed - * @param {import('@appium/types').StringRecord} [args={}] - * @param {import('@appium/types').AppiumLogger?} [log=null] + * @param {import('./types').AppleDevice} device + * @param {import('./types').XcodeBuildArgs} args + * @param {import('@appium/types').AppiumLogger | null} [log=null] */ - constructor (xcodeVersion, device, args = {}, log = null) { + constructor (xcodeVersion, device, args, log = null) { this.xcodeVersion = xcodeVersion; this.device = device; @@ -110,13 +109,19 @@ export class XcodeBuild { this.noSessionProxy = noSessionProxy; if (this.useXctestrunFile) { - const deviveInfo = { - isRealDevice: this.realDevice, + /** @type {import('./utils').DeviceInfo} */ + const deviceInfo = { + isRealDevice: !!this.realDevice, udid: this.device.udid, - platformVersion: this.platformVersion, - platformName: this.platformName + platformVersion: this.platformVersion || '', + platformName: this.platformName || '' }; - this.xctestrunFilePath = await setXctestrunFile(deviveInfo, this.iosSdkVersion, this.bootstrapPath, this.wdaRemotePort); + this.xctestrunFilePath = await setXctestrunFile( + deviceInfo, + this.iosSdkVersion || '', + this.bootstrapPath, + this.wdaRemotePort || 8100 + ); return; } @@ -200,8 +205,8 @@ export class XcodeBuild { * @returns {Promise} */ async cleanProject () { - const libScheme = isTvOS(this.platformName) ? LIB_SCHEME_TV : LIB_SCHEME_IOS; - const runnerScheme = isTvOS(this.platformName) ? RUNNER_SCHEME_TV : RUNNER_SCHEME_IOS; + const libScheme = isTvOS(this.platformName || '') ? LIB_SCHEME_TV : LIB_SCHEME_IOS; + const runnerScheme = isTvOS(this.platformName || '') ? RUNNER_SCHEME_TV : RUNNER_SCHEME_IOS; for (const scheme of [libScheme, runnerScheme]) { this.log.debug(`Cleaning the project scheme '${scheme}' to make sure there are no leftovers from previous installs`); @@ -249,7 +254,7 @@ export class XcodeBuild { if (this.useXctestrunFile && this.xctestrunFilePath) { args.push('-xctestrun', this.xctestrunFilePath); } else { - const runnerScheme = isTvOS(this.platformName) ? RUNNER_SCHEME_TV : RUNNER_SCHEME_IOS; + const runnerScheme = isTvOS(this.platformName || '') ? RUNNER_SCHEME_TV : RUNNER_SCHEME_IOS; args.push('-project', this.agentPath, '-scheme', runnerScheme); if (this.derivedDataPath) { args.push('-derivedDataPath', this.derivedDataPath); @@ -257,14 +262,16 @@ export class XcodeBuild { } args.push('-destination', `id=${this.device.udid}`); - const versionMatch = new RegExp(/^(\d+)\.(\d+)/).exec(this.platformVersion); - if (versionMatch) { + let versionMatch; + if (this.platformVersion && (versionMatch = new RegExp(/^(\d+)\.(\d+)/).exec(this.platformVersion))) { args.push( - `${isTvOS(this.platformName) ? 'TV' : 'IPHONE'}OS_DEPLOYMENT_TARGET=${versionMatch[1]}.${versionMatch[2]}` + `${isTvOS(this.platformName || '') ? 'TV' : 'IPHONE'}OS_DEPLOYMENT_TARGET=${versionMatch[1]}.${versionMatch[2]}` ); } else { - this.log.warn(`Cannot parse major and minor version numbers from platformVersion "${this.platformVersion}". ` + - 'Will build for the default platform instead'); + this.log.warn( + `Cannot parse major and minor version numbers from platformVersion "${this.platformVersion}". ` + + 'Will build for the default platform instead' + ); } if (this.realDevice) { @@ -413,10 +420,11 @@ export class XcodeBuild { */ async waitForStart (timer) { // try to connect once every 0.5 seconds, until `launchTimeout` is up - this.log.debug(`Waiting up to ${this.launchTimeout}ms for WebDriverAgent to start`); + const timeout = this.launchTimeout || 60000; // Default to 60 seconds if not set + this.log.debug(`Waiting up to ${timeout}ms for WebDriverAgent to start`); let currentStatus = null; try { - const retries = Math.trunc(this.launchTimeout / 500); + const retries = Math.trunc(timeout / 500); await retryInterval(retries, 1000, async () => { if (this._didProcessExit) { // there has been an error elsewhere and we need to short-circuit @@ -448,7 +456,7 @@ export class XcodeBuild { } catch (err) { this.log.debug(err.stack); throw new Error( - `We were not able to retrieve the /status response from the WebDriverAgent server after ${this.launchTimeout}ms timeout.` + + `We were not able to retrieve the /status response from the WebDriverAgent server after ${timeout}ms timeout.` + `Try to increase the value of 'appium:wdaLaunchTimeout' capability as a possible workaround.` ); } diff --git a/test/unit/webdriveragent-specs.js b/test/unit/webdriveragent-specs.js index 8c00390ea..a9179062a 100644 --- a/test/unit/webdriveragent-specs.js +++ b/test/unit/webdriveragent-specs.js @@ -6,7 +6,12 @@ import _ from 'lodash'; import sinon from 'sinon'; const fakeConstructorArgs = { - device: 'some sim', + device: { + udid: 'some-sim-udid', + simctl: {}, + devicectl: {}, + idb: null + }, platformVersion: '9', host: 'me', port: '5000', @@ -186,7 +191,7 @@ describe('setupCaching()', function () { const getTimestampStub = sinon.stub(utils, 'getWDAUpgradeTimestamp'); beforeEach(function () { - wda = new WebDriverAgent('1'); + wda = new WebDriverAgent('1', fakeConstructorArgs); wdaStub = sinon.stub(wda, 'getStatus'); wdaStubUninstall = sinon.stub(wda, 'uninstall'); }); @@ -249,7 +254,7 @@ describe('setupCaching()', function () { }); it('should not call uninstall since bundle id is equal to updatedWDABundleId capability', async function () { - wda = new WebDriverAgent('1', { updatedWDABundleId: 'com.example.WebDriverAgent' }); + wda = new WebDriverAgent('1', { ...fakeConstructorArgs, updatedWDABundleId: 'com.example.WebDriverAgent' }); wdaStub = sinon.stub(wda, 'getStatus'); wdaStubUninstall = sinon.stub(wda, 'uninstall');