Skip to content

Commit

Permalink
feat: add checkVersion option for mobile:installApp (#2322)
Browse files Browse the repository at this point in the history
* feat: consider installed apps

* follows only with enforceAppInstall

* docs: add enforceAppInstall

* docs: add description more

* add false as example

* fix lint

* rename to check only installation skip

* adjust argument name

* add typedoc

* address error for older one

* fix lint

* use XCUITestDriverOpts

* remove arg

* use Pick to define typedef and property
  • Loading branch information
KazuCocoa authored Feb 9, 2024
1 parent f6d9016 commit dcc96a9
Show file tree
Hide file tree
Showing 4 changed files with 45 additions and 6 deletions.
1 change: 1 addition & 0 deletions docs/reference/execute-methods.md
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,7 @@ Name | Type | Required | Description | Example
app | string | yes | See the description of the `appium:app` capability | /path/to/my.app
timeoutMs | number | no | The maximum time to wait until app install is finished in milliseconds on real devices. If not provided then the value of `appium:appPushTimeout` capability is used. If the capability is not provided then equals to 240000ms | 500000
strategy | string | no | One of possible app installation strategies on real devices. This argument is ignored on simulators. If not provided then the value of `appium:appInstallStrategy` is used. If the latter is also not provided then `serial` is used. See the description of `appium:appInstallStrategy` capability for more details on available values. | parallel
checkVersion | bool | no | If set to `true`, it will make xcuitest driver to verify whether the app version currently installed on the device under test is older than the one, which is provided as `app` value. No app install is going to happen if the candidate app has the same or older version number than the already installed copy of it. The version number used for comparison must be provided as [CFBundleVersion](https://developer.apple.com/documentation/bundleresources/information_property_list/cfbundleversion) [Semantic Versioning](https://semver.org/)-compatible value in the application's `Info.plist`. No validation is performed and the `app` is installed if `checkVersion` was not provided or `false`, which is default behavior. | true

### mobile: isAppInstalled

Expand Down
20 changes: 18 additions & 2 deletions lib/commands/app-management.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {errors} from 'appium/driver';
import {services} from 'appium-ios-device';
import path from 'node:path';
import B from 'bluebird';
import { extractBundleId } from '../app-utils';

export default {
/**
Expand All @@ -13,22 +14,37 @@ export default {
* @param {string} app - See docs for `appium:app` capability
* @param {import('./types').AppInstallStrategy} [strategy] - One of possible app installation strategies on real devices. This argument is ignored on simulators. If not provided, then the value of `appium:appInstallStrategy` is used. If the latter is also not provided, then `serial` is used. See the description of `appium:appInstallStrategy` capability for more details on allowed values.
* @param {number} [timeoutMs] - The maximum time to wait until app install is finished (in ms) on real devices. If not provided, then the value of `appium:appPushTimeout` capability is used. If the capability is not provided then the default is 240000ms (4 minutes).
* @param {boolean} [checkVersion] - If the application installation follows currently installed application's version status if provided. No checking occurs if no this option.
* @privateRemarks Link to capability docs
* @returns {Promise<void>}
* @this {XCUITestDriver}
*/
async mobileInstallApp(app, timeoutMs, strategy) {
async mobileInstallApp(app, timeoutMs, strategy, checkVersion) {
const srcAppPath = await this.helpers.configureApp(app, '.app');
this.log.info(
`Installing '${srcAppPath}' to the ${this.isRealDevice() ? 'real device' : 'Simulator'} ` +
// @ts-expect-error - do not assign arbitrary properties to `this.opts`
`with UDID '${this.opts.device.udid}'`,
);
if (!(await fs.exists(srcAppPath))) {
this.log.errorAndThrow(
throw this.log.errorWithException(
`The application at '${srcAppPath}' does not exist or is not accessible`,
);
}

if (checkVersion) {
const bundleId = await extractBundleId(srcAppPath);
const {install} = await this.checkAutInstallationState(
// @ts-expect-error - do not assign arbitrary properties to `this.opts`
{enforceAppInstall: false, fullReset: false, noReset: false, bundleId, device: this.opts.device, app: srcAppPath}
);

if (!install) {
this.log.info(`Skipping the installation of '${bundleId}'`);
return;
}
}

// @ts-expect-error - do not assign arbitrary properties to `this.opts`
await this.opts.device.installApp(
srcAppPath,
Expand Down
28 changes: 25 additions & 3 deletions lib/driver.js
Original file line number Diff line number Diff line change
Expand Up @@ -1588,9 +1588,31 @@ class XCUITestDriver extends BaseDriver {
return true;
}

async checkAutInstallationState() {
// @ts-expect-error - do not assign arbitrary properties to `this.opts`
const {enforceAppInstall, fullReset, noReset, bundleId, device, app} = this.opts;
/**
* @typedef {Object} AutInstallationStateOptions
* @property {Pick<XCUITestDriverOpts, "enforceAppInstall">} enforceAppInstall
* @property {Pick<XCUITestDriverOpts, "fullReset">} fullReset
* @property {Pick<XCUITestDriverOpts, "noReset">} noReset
* @property {Pick<XCUITestDriverOpts, "bundleId">} bundleId
* @property {Pick<XCUITestDriverOpts, "app">} app
* @property {Object} device - Real device object or simulator object
*/

/**
* @typedef {Object} AutInstallationState
* @property {boolean} install - If the given app should install, or not need to install.
* @property {boolean} skipUninstall - If the installed app should be uninstalled, or not.
* /
/**
* Check if the given app can be installed, or should uninstall before installing it.
*
* @param {AutInstallationStateOptions} [opts]
* @returns {Promise<AutInstallationState>}
*/
async checkAutInstallationState(opts) {
// @ts-expect-error - do not assign arbitrary properties to `opts`
const {enforceAppInstall, fullReset, noReset, bundleId, device, app} = opts ?? this.opts;

const wasAppInstalled = await device.isAppInstalled(bundleId);
if (wasAppInstalled) {
Expand Down
2 changes: 1 addition & 1 deletion lib/execute-method-map.ts
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ export const executeMethodMap = {
command: 'mobileInstallApp',
params: {
required: ['app'],
optional: ['strategy', 'timeoutMs'],
optional: ['strategy', 'timeoutMs', 'checkVersion'],
},
},
'mobile: isAppInstalled': {
Expand Down

0 comments on commit dcc96a9

Please sign in to comment.