appium-xcuitest-driver
Version:
Appium driver for iOS using XCUITest for backend
176 lines • 6.96 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.AppInfosCache = void 0;
const lodash_1 = __importDefault(require("lodash"));
const node_path_1 = __importDefault(require("node:path"));
const support_1 = require("appium/support");
const lru_cache_1 = require("lru-cache");
const bluebird_1 = __importDefault(require("bluebird"));
const MANIFEST_CACHE = new lru_cache_1.LRUCache({
max: 40,
updateAgeOnHas: true,
});
const MANIFEST_FILE_NAME = 'Info.plist';
const IPA_ROOT_PLIST_PATH_PATTERN = new RegExp(`^Payload/[^/]+\\.app/${lodash_1.default.escapeRegExp(MANIFEST_FILE_NAME)}$`);
const MAX_MANIFEST_SIZE = 1024 * 1024; // 1 MiB
class AppInfosCache {
log;
constructor(log) {
this.log = log;
}
/**
*
* @param bundlePath Full path to the .ipa or .app bundle
* @param propertyName
* @returns
*/
async extractManifestProperty(bundlePath, propertyName) {
const result = (await this.put(bundlePath))[propertyName];
this.log.debug(`${propertyName}: ${JSON.stringify(result)}`);
return result;
}
/**
*
* @param bundlePath Full path to the .ipa or .app bundle
* @returns
*/
async extractBundleId(bundlePath) {
return await this.extractManifestProperty(bundlePath, 'CFBundleIdentifier');
}
/**
*
* @param bundlePath Full path to the .ipa or .app bundle
* @returns
*/
async extractBundleVersion(bundlePath) {
return await this.extractManifestProperty(bundlePath, 'CFBundleVersion');
}
/**
*
* @param bundlePath Full path to the .ipa or .app bundle
* @returns
*/
async extractAppPlatforms(bundlePath) {
const result = await this.extractManifestProperty(bundlePath, 'CFBundleSupportedPlatforms');
if (!Array.isArray(result)) {
throw new Error(`${node_path_1.default.basename(bundlePath)}': CFBundleSupportedPlatforms is not a valid list`);
}
return result;
}
/**
*
* @param bundlePath Full path to the .ipa or .app bundle
* @returns
*/
async extractExecutableName(bundlePath) {
return await this.extractManifestProperty(bundlePath, 'CFBundleExecutable');
}
/**
*
* @param bundlePath Full path to the .ipa or .app bundle
* @returns The payload of the manifest plist
* @throws If the given app is not a valid bundle
*/
async put(bundlePath) {
return (await support_1.fs.stat(bundlePath)).isFile()
? await this._putIpa(bundlePath)
: await this._putApp(bundlePath);
}
/**
* @param ipaPath Fill path to the .ipa bundle
* @returns The payload of the manifest plist
*/
async _putIpa(ipaPath) {
let manifestPayload;
let lastError;
try {
await support_1.zip.readEntries(ipaPath, async ({ entry, extractEntryTo }) => {
// For a future reference:
// If the directory name includes `.app` suffix (case insensitive) like 'Payload/something.App.app/filename',
// then 'entry.fileName' would return 'Payload/something.App/filename'.
// The behavior is specific for unzip. Technically such naming is possible and valid,
// although Info.plist retrieval would fail in Appium.
// https://github.com/appium/appium/issues/20075
if (!IPA_ROOT_PLIST_PATH_PATTERN.test(entry.fileName)) {
return true;
}
const hash = `${entry.crc32}`;
if (MANIFEST_CACHE.has(hash)) {
manifestPayload = MANIFEST_CACHE.get(hash);
return false;
}
const tmpRoot = await support_1.tempDir.openDir();
try {
await extractEntryTo(tmpRoot);
const plistPath = node_path_1.default.resolve(tmpRoot, entry.fileName);
manifestPayload = await this._readPlist(plistPath, ipaPath);
if (lodash_1.default.isPlainObject(manifestPayload) && entry.uncompressedSize <= MAX_MANIFEST_SIZE) {
this.log.debug(`Caching the manifest '${entry.fileName}' for ${manifestPayload?.CFBundleIdentifier} app ` +
`from the compressed source using the key '${hash}'`);
MANIFEST_CACHE.set(hash, manifestPayload);
}
}
catch (e) {
this.log.debug(e.stack);
lastError = e;
}
finally {
await support_1.fs.rimraf(tmpRoot);
}
return false;
});
}
catch (e) {
this.log.debug(e.stack);
throw new Error(`Cannot find ${MANIFEST_FILE_NAME} in '${ipaPath}'. Is it a valid application bundle?`);
}
if (!manifestPayload) {
let errorMessage = `Cannot extract ${MANIFEST_FILE_NAME} from '${ipaPath}'. Is it a valid application bundle?`;
if (lastError) {
errorMessage += ` Original error: ${lastError.message}`;
}
throw new Error(errorMessage);
}
return manifestPayload;
}
/**
* @param appPath Fill path to the .app bundle
* @returns The payload of the manifest plist
*/
async _putApp(appPath) {
const manifestPath = node_path_1.default.join(appPath, MANIFEST_FILE_NAME);
const hash = await support_1.fs.hash(manifestPath);
const cached = MANIFEST_CACHE.get(hash);
if (cached) {
return cached;
}
const [payload, stat] = await bluebird_1.default.all([
this._readPlist(manifestPath, appPath),
support_1.fs.stat(manifestPath),
]);
if (stat.size <= MAX_MANIFEST_SIZE && lodash_1.default.isPlainObject(payload)) {
this.log.debug(`Caching the manifest for ${payload.CFBundleIdentifier} app from a file source using the key '${hash}'`);
MANIFEST_CACHE.set(hash, payload);
}
return payload;
}
/**
* @param plistPath Full path to the plist
* @param bundlePath Full path to .ipa or .app bundle
* @returns The payload of the plist file
*/
async _readPlist(plistPath, bundlePath) {
try {
return await support_1.plist.parsePlistFile(plistPath);
}
catch (e) {
this.log.debug(e.stack);
throw new Error(`Cannot parse ${MANIFEST_FILE_NAME} of '${bundlePath}'. Is it a valid application bundle?`);
}
}
}
exports.AppInfosCache = AppInfosCache;
//# sourceMappingURL=app-infos-cache.js.map