appium-ios-simulator
Version:
iOS Simulator interface for Appium.
177 lines (163 loc) • 5.19 kB
text/typescript
import path from 'node:path';
import {fs, plist, util} from '@appium/support';
import {waitForCondition} from 'asyncbox';
import {isPlainObject} from '../utils';
import type {CoreSimulator, InteractsWithApps, LaunchAppOptions} from '../types';
type CoreSimulatorWithApps = CoreSimulator & InteractsWithApps;
/**
* Install valid .app package on Simulator.
*
* @param app The path to the .app package.
*/
export async function installApp(this: CoreSimulatorWithApps, app: string): Promise<void> {
return await this.simctl.installApp(app);
}
/**
* Returns user installed bundle ids which has 'bundleName' in their Info.Plist as 'CFBundleName'
*
* @param bundleName The bundle name of the application to be checked.
* @return The list of bundle ids which have 'bundleName'
*/
export async function getUserInstalledBundleIdsByBundleName(
this: CoreSimulatorWithApps,
bundleName: string,
): Promise<string[]> {
const appsRoot = path.resolve(this.getDir(), 'Containers', 'Bundle', 'Application');
// glob all Info.plist from simdir/data/Containers/Bundle/Application
const infoPlists = await fs.glob('*/*.app/Info.plist', {
cwd: appsRoot,
absolute: true,
});
if (infoPlists.length === 0) {
return [];
}
const bundleInfoPromises: Promise<any>[] = [];
for (const infoPlist of infoPlists) {
bundleInfoPromises.push(
(async () => {
try {
return await plist.parsePlistFile(infoPlist);
} catch {
return null;
}
})(),
);
}
const bundleInfos = (await Promise.all(bundleInfoPromises)).filter(isPlainObject);
const bundleIds = bundleInfos
.filter(({CFBundleName}) => CFBundleName === bundleName)
.map(({CFBundleIdentifier}) => CFBundleIdentifier);
if (bundleIds.length === 0) {
return [];
}
this.log.debug(
`The simulator has ${util.pluralize('bundle', bundleIds.length, true)} which ` +
`have '${bundleName}' as their 'CFBundleName': ${JSON.stringify(bundleIds)}`,
);
return bundleIds;
}
/**
* Verify whether the particular application is installed on Simulator.
*
* @param bundleId The bundle id of the application to be checked.
* @return True if the given application is installed.
*/
export async function isAppInstalled(
this: CoreSimulatorWithApps,
bundleId: string,
): Promise<boolean> {
try {
const appContainer = await this.simctl.getAppContainer(bundleId);
if (!appContainer.endsWith('.app')) {
return false;
}
return await fs.exists(appContainer);
} catch {
// get_app_container subcommand fails for system applications,
// so we try the hidden appinfo subcommand, which prints correct info for
// system/hidden apps
try {
await this.simctl.appInfo(bundleId);
return true;
} catch {
return false;
}
}
}
/**
* Uninstall the given application from the current Simulator.
*
* @param bundleId The bundle ID of the application to be removed.
*/
export async function removeApp(this: CoreSimulatorWithApps, bundleId: string): Promise<void> {
await this.simctl.removeApp(bundleId);
}
/**
* Starts the given application on Simulator
*
* @param bundleId The bundle ID of the application to be launched
* @param opts Launch options
*/
export async function launchApp(
this: CoreSimulatorWithApps,
bundleId: string,
opts: LaunchAppOptions = {},
): Promise<void> {
await this.simctl.launchApp(bundleId);
const {wait = false, timeoutMs = 10000} = opts;
if (!wait) {
return;
}
try {
await waitForCondition(async () => await this.isAppRunning(bundleId), {
waitMs: timeoutMs,
intervalMs: 300,
});
} catch {
throw new Error(`App '${bundleId}' is not runnning after ${timeoutMs}ms timeout.`);
}
}
/**
* Stops the given application on Simulator.
*
* @param bundleId The bundle ID of the application to be stopped
*/
export async function terminateApp(this: CoreSimulatorWithApps, bundleId: string): Promise<void> {
await this.simctl.terminateApp(bundleId);
}
/**
* Check if app with the given identifier is running.
*
* @param bundleId The bundle ID of the application to be checked.
*/
export async function isAppRunning(
this: CoreSimulatorWithApps,
bundleId: string,
): Promise<boolean> {
return (await this.ps()).some(({name}) => name === bundleId);
}
/**
* Scrub (delete the preferences and changed files) the particular application on Simulator.
* The app will be terminated automatically if it is running.
*
* @param bundleId Bundle identifier of the application.
* @throws {Error} if the given app is not installed.
*/
export async function scrubApp(this: CoreSimulatorWithApps, bundleId: string): Promise<void> {
const appDataRoot = await this.simctl.getAppContainer(bundleId, 'data');
const appFiles = await fs.glob('**/*', {
cwd: appDataRoot,
nodir: true,
absolute: true,
});
this.log.info(
`Found ${appFiles.length} ${bundleId} app ${util.pluralize('file', appFiles.length, false)} to scrub`,
);
if (appFiles.length === 0) {
return;
}
try {
await this.terminateApp(bundleId);
} catch {}
await Promise.all(appFiles.map((p) => fs.rimraf(p)));
}