UNPKG

appium-espresso-driver

Version:
153 lines 6.7 kB
import { utils } from 'appium-android-driver'; import { fs, tempDir, zip } from 'appium/support.js'; import { SETTINGS_HELPER_ID } from 'io.appium.settings'; import path from 'node:path'; import { TEST_APK_PKG } from './server/index.js'; import { isPlainObject } from '../utils/index.js'; export const APK_EXT = '.apk'; export const AAB_EXT = '.aab'; export const SUPPORTED_EXTENSIONS = [APK_EXT, AAB_EXT]; /** Whether the session targets an app already installed on the device (no app path). */ export function isAppOnDevice(driver) { const appPackage = driver.opts.appPackage; return !driver.opts.app && !!appPackage && driver.helpers.isPackageOrBundle(appPackage); } /** * Unzip the given app path and return the first package that has SUPPORTED_EXTENSIONS * in the archived file. * * @param appPath The path to app file. * @returns The path to an unzipped app file path. * @throws If the zip did not have any SUPPORTED_EXTENSIONS packages. */ export async function unzipApp(appPath) { const useSystemUnzipEnv = process.env.APPIUM_PREFER_SYSTEM_UNZIP; const useSystemUnzip = !useSystemUnzipEnv || !['0', 'false'].includes(useSystemUnzipEnv.toLowerCase()); const tmpRoot = await tempDir.openDir(); await zip.extractAllTo(appPath, tmpRoot, { useSystemUnzip }); const globPattern = `**/*.+(${SUPPORTED_EXTENSIONS.map((ext) => ext.replace(/^\./, '')).join('|')})`; const sortedBundleItems = (await fs.glob(globPattern, { cwd: tmpRoot, })).sort((a, b) => a.split(path.sep).length - b.split(path.sep).length); if (sortedBundleItems.length === 0) { throw this.log.errorWithException(`${this.opts.app} did not have any of '${SUPPORTED_EXTENSIONS.join(', ')}' ` + `extension packages. Please make sure the provided .zip archive contains at ` + `least one valid application package.`); } const unzippedAppPath = path.join(tmpRoot, sortedBundleItems[0]); this.log.debug(`'${unzippedAppPath}' is the unzipped file from '${appPath}'`); return unzippedAppPath; } /** Post-processes configured apps and reuses a valid cache entry when possible. */ export async function onPostConfigureApp(opts) { const { cachedAppInfo, isUrl, appPath } = opts; if (!appPath) { return undefined; } const presignApp = async (appLocation) => { if (this.opts.noSign) { this.log.info('Skipping application signing because noSign capability is set to true. ' + 'Having the application under test with improper signature/non-signed will cause ' + 'Espresso automation startup failure.'); } else if (!(await this.adb.checkApkCert(appLocation))) { await this.adb.sign(appLocation); } }; const hasApkExt = (p) => p.toLowerCase().endsWith(APK_EXT); const hasAabExt = (p) => p.toLowerCase().endsWith(AAB_EXT); const extractUniversalApk = async (shouldExtract, p) => shouldExtract ? p : await this.adb.extractUniversalApk(p); let pathInCache = null; let isResultAppPathAlreadyCached = false; if (isCachedAppInfo(cachedAppInfo)) { const packageHash = await fs.hash(appPath); if (packageHash === cachedAppInfo.packageHash && (await fs.exists(cachedAppInfo.fullPath))) { this.log.info(`Using '${cachedAppInfo.fullPath}' which is cached from '${appPath}'`); isResultAppPathAlreadyCached = true; pathInCache = cachedAppInfo.fullPath; } } const isApk = hasApkExt(appPath); const shouldResultAppPathBeCached = !isApk || (isApk && isUrl); if (!isResultAppPathAlreadyCached) { if (shouldResultAppPathBeCached) { let unzippedAppPath; let isUnzippedApk = false; if (!(hasApkExt(appPath) || hasAabExt(appPath))) { unzippedAppPath = await this.unzipApp(appPath); isUnzippedApk = hasApkExt(unzippedAppPath); } pathInCache = unzippedAppPath ? await extractUniversalApk(isUnzippedApk, unzippedAppPath) : await extractUniversalApk(isApk, appPath); if (!isApk && isUrl) { await fs.rimraf(appPath); } if (unzippedAppPath !== undefined && hasAabExt(unzippedAppPath)) { await fs.rimraf(unzippedAppPath); } if (pathInCache == null) { throw this.log.errorWithException('Expected a cached app path after post-processing'); } await presignApp(pathInCache); } else if (isApk) { await presignApp(appPath); } } return shouldResultAppPathBeCached && pathInCache != null ? { appPath: pathInCache } : undefined; } /** Prepare the application under test on the device. */ export async function initAUT() { if (this.opts.uninstallOtherPackages) { await this.uninstallOtherPackages(utils.parseArray(this.opts.uninstallOtherPackages), [ SETTINGS_HELPER_ID, TEST_APK_PKG, ]); } if (!this.opts.app) { if (this.opts.fullReset) { throw this.log.errorWithException('Full reset requires an app capability, use fastReset if app is not provided'); } this.log.debug('No app capability. Assuming it is already on the device'); if (this.opts.fastReset) { await this.resetAUT(); } } const appPackage = this.opts.appPackage; if (!appPackage) { throw this.log.errorWithException('appPackage is required'); } if (!this.opts.skipUninstall) { await this.adb.uninstallApk(appPackage); } if (this.opts.app) { await this.installAUT(); } if (this.opts.skipServerInstallation) { this.log.debug('skipServerInstallation capability is set. Not installig espresso-server'); } else { await this.espresso.installTestApk(); } try { await this.adb.addToDeviceIdleWhitelist(SETTINGS_HELPER_ID, TEST_APK_PKG); } catch (e) { const stderr = typeof e === 'object' && e !== null && 'stderr' in e && typeof e.stderr === 'string' ? e.stderr : undefined; const message = e instanceof Error ? e.message : String(e); this.log.warn(`Cannot add server packages to the Doze whitelist. Original error: ` + (stderr || message)); } } /** Type guard for cached app metadata shape. */ function isCachedAppInfo(value) { return (isPlainObject(value) && typeof value.packageHash === 'string' && typeof value.fullPath === 'string'); } //# sourceMappingURL=app-install.js.map