appium-espresso-driver
Version:
Espresso integration for Appium
689 lines • 35.2 kB
JavaScript
;
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.EspressoDriver = void 0;
const lodash_1 = __importDefault(require("lodash"));
const path_1 = __importDefault(require("path"));
const bluebird_1 = __importDefault(require("bluebird"));
const driver_1 = require("appium/driver");
const espresso_runner_1 = require("./espresso-runner");
const support_1 = require("appium/support");
const appManagementCmds = __importStar(require("./commands/app-management"));
const contextCmds = __importStar(require("./commands/context"));
const elementCmds = __importStar(require("./commands/element"));
const miscCmds = __importStar(require("./commands/misc"));
const servicesCmds = __importStar(require("./commands/services"));
const screenshotCmds = __importStar(require("./commands/screenshot"));
const idlingResourcesCmds = __importStar(require("./commands/idling-resources"));
const actionsCmds = __importStar(require("./commands/actions"));
const clipboardCmds = __importStar(require("./commands/clipboard"));
const appium_adb_1 = require("appium-adb");
const appium_android_driver_1 = require("appium-android-driver");
const io_appium_settings_1 = require("io.appium.settings");
const constraints_1 = require("./constraints");
const portscanner_1 = require("portscanner");
const asyncbox_1 = require("asyncbox");
const utils_1 = require("./utils");
const method_map_1 = require("./method-map");
const execute_method_map_1 = require("./execute-method-map");
// The range of ports we can use on the system for communicating to the
// Espresso HTTP server on the device
const SYSTEM_PORT_RANGE = [8300, 8399];
// This is the port that the espresso server listens to on the device. We will
// forward one of the ports above on the system to this port on the device.
const DEVICE_PORT = 6791;
// NO_PROXY contains the paths that we never want to proxy to espresso server.
// TODO: Add the list of paths that we never want to proxy to espresso server.
// TODO: Need to segregate the paths better way using regular expressions wherever applicable.
// (Not segregating right away because more paths to be added in the NO_PROXY list)
const NO_PROXY = [
['GET', new RegExp('^/session/(?!.*/)')],
['GET', new RegExp('^/session/[^/]+/appium/capabilities')],
['GET', new RegExp('^/session/[^/]+/appium/commands')],
['GET', new RegExp('^/session/[^/]+/appium/device/current_activity')],
['GET', new RegExp('^/session/[^/]+/appium/device/current_package')],
['GET', new RegExp('^/session/[^/]+/appium/device/display_density')],
['GET', new RegExp('^/session/[^/]+/appium/device/is_keyboard_shown')],
['GET', new RegExp('^/session/[^/]+/appium/device/system_bars')],
['GET', new RegExp('^/session/[^/]+/appium/device/system_time')],
['GET', new RegExp('^/session/[^/]+/appium/extensions')],
['GET', new RegExp('^/session/[^/]+/appium/settings')],
['GET', new RegExp('^/session/[^/]+/context')],
['GET', new RegExp('^/session/[^/]+/contexts')],
['GET', new RegExp('^/session/[^/]+/ime/[^/]+')],
['GET', new RegExp('^/session/[^/]+/network_connection')],
['GET', new RegExp('^/session/[^/]+/timeouts')],
['GET', new RegExp('^/session/[^/]+/url')],
['POST', new RegExp('^/session/[^/]+/appium/app/background')],
['POST', new RegExp('^/session/[^/]+/appium/app/close')],
['POST', new RegExp('^/session/[^/]+/appium/app/launch')],
['POST', new RegExp('^/session/[^/]+/appium/app/reset')],
['POST', new RegExp('^/session/[^/]+/appium/app/strings')],
['POST', new RegExp('^/session/[^/]+/appium/compare_images')],
['POST', new RegExp('^/session/[^/]+/appium/device/activate_app')],
['POST', new RegExp('^/session/[^/]+/appium/device/app_installed')],
['POST', new RegExp('^/session/[^/]+/appium/device/app_state')],
['POST', new RegExp('^/session/[^/]+/appium/device/finger_print')],
['POST', new RegExp('^/session/[^/]+/appium/device/get_clipboard')],
['POST', new RegExp('^/session/[^/]+/appium/device/install_app')],
['POST', new RegExp('^/session/[^/]+/appium/device/is_locked')],
['POST', new RegExp('^/session/[^/]+/appium/device/lock')],
['POST', new RegExp('^/session/[^/]+/appium/device/pull_file')],
['POST', new RegExp('^/session/[^/]+/appium/device/pull_folder')],
['POST', new RegExp('^/session/[^/]+/appium/device/push_file')],
['POST', new RegExp('^/session/[^/]+/appium/device/remove_app')],
['POST', new RegExp('^/session/[^/]+/appium/device/start_activity')],
['POST', new RegExp('^/session/[^/]+/appium/device/terminate_app')],
['POST', new RegExp('^/session/[^/]+/appium/device/unlock')],
['POST', new RegExp('^/session/[^/]+/appium/getPerformanceData')],
['POST', new RegExp('^/session/[^/]+/appium/performanceData/types')],
['POST', new RegExp('^/session/[^/]+/appium/settings')],
['POST', new RegExp('^/session/[^/]+/appium/execute_driver')],
['POST', new RegExp('^/session/[^/]+/appium/start_recording_screen')],
['POST', new RegExp('^/session/[^/]+/appium/stop_recording_screen')],
['POST', new RegExp('^/session/[^/]+/context')],
['POST', new RegExp('^/session/[^/]+/execute')],
['POST', new RegExp('^/session/[^/]+/execute/async')],
['POST', new RegExp('^/session/[^/]+/execute/sync')],
['POST', new RegExp('^/session/[^/]+/execute_async')],
['POST', new RegExp('^/session/[^/]+/ime/[^/]+')],
['POST', new RegExp('^/session/[^/]+/location')],
['POST', new RegExp('^/session/[^/]+/network_connection')],
['POST', new RegExp('^/session/[^/]+/timeouts')],
['POST', new RegExp('^/session/[^/]+/url')],
// MJSONWP commands
['GET', new RegExp('^/session/[^/]+/log/types')],
['POST', new RegExp('^/session/[^/]+/log')],
// W3C commands
// For Selenium v4 (W3C does not have this route)
['GET', new RegExp('^/session/[^/]+/se/log/types')],
// For Selenium v4 (W3C does not have this route)
['POST', new RegExp('^/session/[^/]+/se/log')],
];
// This is a set of methods and paths that we never want to proxy to Chromedriver.
const CHROME_NO_PROXY = [
['GET', new RegExp('^/session/[^/]+/appium')],
['GET', new RegExp('^/session/[^/]+/context')],
['GET', new RegExp('^/session/[^/]+/element/[^/]+/rect')],
['GET', new RegExp('^/session/[^/]+/orientation')],
['POST', new RegExp('^/session/[^/]+/appium')],
['POST', new RegExp('^/session/[^/]+/context')],
['POST', new RegExp('^/session/[^/]+/orientation')],
['POST', new RegExp('^/session/[^/]+/touch/multi/perform')],
['POST', new RegExp('^/session/[^/]+/touch/perform')],
// this is needed to make the mobile: commands working in web context
['POST', new RegExp('^/session/[^/]+/execute$')],
['POST', new RegExp('^/session/[^/]+/execute/sync')],
// MJSONWP commands
['GET', new RegExp('^/session/[^/]+/log/types')],
['POST', new RegExp('^/session/[^/]+/log')],
// W3C commands
// For Selenium v4 (W3C does not have this route)
['GET', new RegExp('^/session/[^/]+/se/log/types')],
// For Selenium v4 (W3C does not have this route)
['POST', new RegExp('^/session/[^/]+/se/log')],
];
const APK_EXT = '.apk';
const AAB_EXT = '.aab';
const SUPPORTED_EXTENSIONS = [APK_EXT, AAB_EXT];
class EspressoDriver extends appium_android_driver_1.AndroidDriver {
constructor(opts = {}, shouldValidateCaps = true) {
// `shell` overwrites adb.shell, so remove
// @ts-expect-error FIXME: what is this?
delete opts.shell;
super(opts, shouldValidateCaps);
// @ts-ignore It's expected
this.performActions = actionsCmds.performActions;
this.startActivity = appManagementCmds.startActivity;
// @ts-ignore It's expected
this.mobileStartActivity = appManagementCmds.mobileStartActivity;
this.mobileWebAtoms = contextCmds.mobileWebAtoms;
// @ts-ignore It's expected
this.suspendChromedriverProxy = contextCmds.suspendChromedriverProxy;
// @ts-ignore It's expected
this.mobilePerformEditorAction = elementCmds.mobilePerformEditorAction;
this.mobileSwipe = elementCmds.mobileSwipe;
this.mobileOpenDrawer = elementCmds.mobileOpenDrawer;
this.mobileCloseDrawer = elementCmds.mobileCloseDrawer;
this.mobileSetDate = elementCmds.mobileSetDate;
this.mobileSetTime = elementCmds.mobileSetTime;
this.mobileNavigateTo = elementCmds.mobileNavigateTo;
this.mobileScrollToPage = elementCmds.mobileScrollToPage;
this.mobileFlashElement = elementCmds.mobileFlashElement;
this.mobileClickAction = elementCmds.mobileClickAction;
this.mobileDismissAutofill = elementCmds.mobileDismissAutofill;
this.mobilePressKey = miscCmds.mobilePressKey;
this.mobileGetDeviceInfo = miscCmds.mobileGetDeviceInfo;
this.mobileIsToastVisible = miscCmds.mobileIsToastVisible;
// @ts-ignore It's expected
this.getDisplayDensity = miscCmds.getDisplayDensity;
this.mobileBackdoor = miscCmds.mobileBackdoor;
this.mobileUiautomator = miscCmds.mobileUiautomator;
this.mobileUiautomatorPageSource = miscCmds.mobileUiautomatorPageSource;
this.updateSettings = miscCmds.updateSettings;
this.getSettings = miscCmds.getSettings;
this.getClipboard = clipboardCmds.getClipboard;
this.mobileGetClipboard = clipboardCmds.getClipboard;
this.mobileSetClipboard = clipboardCmds.mobileSetClipboard;
// @ts-ignore It's expected
this.mobileStartService = servicesCmds.mobileStartService;
// @ts-ignore It's expected
this.mobileStopService = servicesCmds.mobileStopService;
this.getScreenshot = screenshotCmds.getScreenshot;
this.mobileScreenshots = screenshotCmds.mobileScreenshots;
this.mobileRegisterIdlingResources = idlingResourcesCmds.mobileRegisterIdlingResources;
this.mobileUnregisterIdlingResources = idlingResourcesCmds.mobileUnregisterIdlingResources;
this.mobileListIdlingResources = idlingResourcesCmds.mobileListIdlingResources;
this.mobileWaitForUIThread = idlingResourcesCmds.mobileWaitForUIThread;
this.locatorStrategies = [
'id',
'class name',
'accessibility id',
];
this.desiredCapConstraints = constraints_1.ESPRESSO_CONSTRAINTS;
this.jwpProxyAvoid = NO_PROXY;
this._originalIme = null;
this.caps = {};
this.opts = opts;
this.settings = new driver_1.DeviceSettings({}, this.onSettingsUpdate.bind(this));
this.chromedriver = undefined;
}
async getSession() {
return await driver_1.BaseDriver.prototype.getSession.call(this);
}
async createSession(w3cCaps1, w3cCaps2, w3cCaps3, driverData) {
try {
const [sessionId, caps] = (await driver_1.BaseDriver.prototype.createSession.call(this, w3cCaps1, w3cCaps2, w3cCaps3, driverData));
const serverDetails = {
platform: 'LINUX',
webStorageEnabled: false,
takesScreenshot: true,
javascriptEnabled: true,
databaseEnabled: false,
networkConnectionEnabled: true,
locationContextEnabled: false,
warnings: {},
desired: Object.assign({}, this.caps)
};
this.caps = Object.assign(serverDetails, this.caps);
this.curContext = this.defaultContextName();
const defaultOpts = {
fullReset: false,
autoLaunch: true,
adbPort: appium_adb_1.DEFAULT_ADB_PORT,
androidInstallTimeout: 90000
};
lodash_1.default.defaults(this.opts, defaultOpts);
if (this.isChromeSession) {
if (this.opts.app) {
this.log.warn(`'browserName' capability will be ignored`);
this.log.warn(`Chrome browser cannot be run in Espresso sessions because Espresso automation doesn't ` +
`have permission to access Chrome`);
}
else {
throw this.log.errorWithException(`Chrome browser sessions cannot be run in Espresso because Espresso ` +
`automation doesn't have permission to access Chrome`);
}
}
this.opts.systemPort = this.opts.systemPort
|| await (0, portscanner_1.findAPortNotInUse)(SYSTEM_PORT_RANGE[0], SYSTEM_PORT_RANGE[1]);
this.opts.adbPort = this.opts.adbPort || appium_adb_1.DEFAULT_ADB_PORT;
// get device udid for this session
const { udid, emPort } = await this.getDeviceInfoFromCaps();
this.opts.udid = udid;
// @ts-expect-error do not put random stuff on opts
this.opts.emPort = emPort;
// now that we know our java version and device info, we can create our
// ADB instance
this.adb = await this.createADB();
if (this.opts.app) {
// find and copy, or download and unzip an app url or path
this.opts.app = await this.helpers.configureApp(this.opts.app, {
onPostProcess: this.onPostConfigureApp.bind(this),
supportedExtensions: SUPPORTED_EXTENSIONS
});
}
else if (this.appOnDevice) {
// the app isn't an actual app file but rather something we want to
// assume is on the device and just launch via the appPackage
this.log.info(`App file was not listed, instead we're going to run ` +
`${this.opts.appPackage} directly on the device`);
if (!await this.adb.isAppInstalled(this.opts.appPackage)) {
throw this.log.errorWithException(`Could not find the package '${this.opts.appPackage}' installed on the device`);
}
}
await this.startEspressoSession();
return [sessionId, caps];
}
catch (e) {
await this.deleteSession();
e.message += `${lodash_1.default.endsWith(e.message, '.') ? '' : '.'} Check ` +
'https://github.com/appium/appium-espresso-driver#troubleshooting ' +
'regarding advanced session startup troubleshooting.';
if ((0, driver_1.isErrorType)(e, driver_1.errors.SessionNotCreatedError)) {
throw e;
}
const err = new driver_1.errors.SessionNotCreatedError(e.message);
err.stack = e.stack;
throw err;
}
}
/**
* Unzip the given app path and return the first package that has SUPPORTED_EXTENSIONS
* in the archived file.
*
* @param {string} appPath The path to app file.
* @returns {Promise<string>} Returns the path to an unzipped app file path.
* @throws Raise an exception if the zip did not have any SUPPORTED_EXTENSIONS packages.
*/
async unzipApp(appPath) {
const useSystemUnzipEnv = process.env.APPIUM_PREFER_SYSTEM_UNZIP;
const useSystemUnzip = lodash_1.default.isEmpty(useSystemUnzipEnv)
|| !['0', 'false'].includes(lodash_1.default.toLower(useSystemUnzipEnv));
const tmpRoot = await support_1.tempDir.openDir();
await support_1.zip.extractAllTo(appPath, tmpRoot, { useSystemUnzip });
const globPattern = `**/*.+(${SUPPORTED_EXTENSIONS.map((ext) => ext.replace(/^\./, '')).join('|')})`;
const sortedBundleItems = (await support_1.fs.glob(globPattern, {
cwd: tmpRoot,
})).sort((a, b) => a.split(path_1.default.sep).length - b.split(path_1.default.sep).length);
if (sortedBundleItems.length === 0) {
// no expected packages in the zip
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_1.default.join(tmpRoot, lodash_1.default.first(sortedBundleItems));
this.log.debug(`'${unzippedAppPath}' is the unzipped file from '${appPath}'`);
return unzippedAppPath;
}
async onPostConfigureApp({ cachedAppInfo, isUrl, appPath }) {
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, this.opts.appPackage)) {
await this.adb.sign(appLocation);
}
};
const hasApkExt = (appPath) => lodash_1.default.endsWith(lodash_1.default.toLower(appPath), APK_EXT);
const hasAabExt = (appPath) => lodash_1.default.endsWith(lodash_1.default.toLower(appPath), AAB_EXT);
const extractUniversalApk = async (shouldExtract, appPath) => shouldExtract ? appPath : await this.adb.extractUniversalApk(appPath);
let pathInCache = null;
let isResultAppPathAlreadyCached = false;
if (lodash_1.default.isPlainObject(cachedAppInfo)) {
const packageHash = await support_1.fs.hash(appPath);
if (packageHash === cachedAppInfo.packageHash && await support_1.fs.exists(cachedAppInfo.fullPath)) {
this.log.info(`Using '${cachedAppInfo.fullPath}' which is cached from '${appPath}'`);
isResultAppPathAlreadyCached = true;
pathInCache = cachedAppInfo.fullPath;
}
}
// appPath can be .zip, .apk or .aab
const isApk = hasApkExt(appPath);
// Only local .apk files that are available in-place should not be cached
const shouldResultAppPathBeCached = !isApk || (isApk && isUrl);
if (!isResultAppPathAlreadyCached) {
if (shouldResultAppPathBeCached) {
// .zip, .aab or downloaded .apk
let unzippedAppPath;
let isUnzippedApk = false;
if (!(hasApkExt(appPath) || hasAabExt(appPath))) {
unzippedAppPath = await this.unzipApp(appPath);
isUnzippedApk = hasApkExt(unzippedAppPath);
}
// unzippedAppPath or appPath has SUPPORTED_EXTENSIONS.
pathInCache = unzippedAppPath
? await extractUniversalApk(isUnzippedApk, unzippedAppPath)
: await extractUniversalApk(isApk, appPath);
if (!isApk && isUrl) {
// Clean up the temporarily downloaded .aab or .zip package
await support_1.fs.rimraf(appPath);
}
if (hasAabExt(unzippedAppPath)) {
// Cleanup the local unzipped .aab file
await support_1.fs.rimraf(/** @type {string} */ (unzippedAppPath));
}
await presignApp(pathInCache);
}
else if (isApk) {
// It is probably not the best idea to modify the provided app in-place,
// but this is how it was always working
await presignApp(appPath);
}
}
return shouldResultAppPathBeCached ? { appPath: pathInCache } : false;
}
get driverData() {
// TODO fille out resource info here
return {};
}
// TODO much of this logic is duplicated from uiautomator2
async startEspressoSession() {
const { manifestPayload } = await (0, utils_1.getPackageInfo)();
this.log.info(`EspressoDriver version: ${manifestPayload.version}`);
// Read https://github.com/appium/appium-android-driver/pull/461 what happens if ther is no setHiddenApiPolicy for Android P+
if (await this.adb.getApiLevel() >= 28) { // Android P
this.log.warn('Relaxing hidden api policy');
await this.adb.setHiddenApiPolicy('1', !!this.opts.ignoreHiddenApiPolicyError);
}
// get appPackage et al from manifest if necessary
let appInfo = await this.getLaunchInfo();
if (appInfo) {
// and get it onto our 'opts' object so we use it from now on
Object.assign(this.opts, appInfo);
}
else {
appInfo = this.opts;
}
// start an avd, set the language/locale, pick an emulator, etc...
if (this.opts.hideKeyboard) {
this._originalIme = await this.adb.defaultIME();
}
await this.initDevice();
// Default state is window animation disabled.
await this.setWindowAnimationState(this.caps.disableWindowAnimation === false);
// set actual device name, udid
this.caps.deviceName = this.adb.curDeviceId;
this.caps.deviceUDID = this.opts.udid;
// set up the modified espresso server etc
this.initEspressoServer();
// Further prepare the device by forwarding the espresso port
this.log.debug(`Forwarding Espresso Server port ${DEVICE_PORT} to ${this.opts.systemPort}`);
await this.adb.forwardPort(this.opts.systemPort, DEVICE_PORT);
if (!this.opts.skipUnlock) {
// unlock the device to prepare it for testing
// @ts-ignore This is ok
await this.unlock();
}
else {
this.log.debug(`'skipUnlock' capability set, so skipping device unlock`);
}
// set up app under test
// prepare our actual AUT, get it on the device, etc...
await this.initAUT();
//Adding AUT package name in the capabilities if package name not exist in caps
if (!this.caps.appPackage) {
this.caps.appPackage = appInfo.appPackage;
}
if (!this.caps.appWaitPackage) {
this.caps.appWaitPackage = appInfo.appWaitPackage || appInfo.appPackage || this.caps.appPackage;
}
if (this.caps.appActivity) {
this.caps.appActivity = (0, utils_1.qualifyActivityName)(this.caps.appActivity, this.caps.appPackage);
}
else {
this.caps.appActivity = (0, utils_1.qualifyActivityName)(appInfo.appActivity, this.caps.appPackage);
}
if (this.caps.appWaitActivity) {
this.caps.appWaitActivity = (0, utils_1.qualifyActivityName)(this.caps.appWaitActivity, this.caps.appWaitPackage);
}
else {
this.caps.appWaitActivity = (0, utils_1.qualifyActivityName)(appInfo.appWaitActivity || appInfo.appActivity || this.caps.appActivity, this.caps.appWaitPackage);
}
// launch espresso and wait till its online and we have a session
await this.espresso.startSession(this.caps);
if (this.caps.autoLaunch === false) {
this.log.info(`Not waiting for the application activity to start because 'autoLaunch' is disabled`);
}
else {
await this.adb.waitForActivity(this.caps.appWaitPackage, this.caps.appWaitActivity, this.opts.appWaitDuration);
}
// if we want to immediately get into a webview, set our context
// appropriately
if (this.opts.autoWebview) {
await this.initWebview();
}
// now that everything has started successfully, turn on proxying so all
// subsequent session requests go straight to/from espresso
this.jwpProxyActive = true;
await this.addDeviceInfoToCaps();
}
/**
* Turn on or off animation scale.
* '--no-window-animation' instrument argument for Espresso disables window animations,
* but it does not bring the animation scale back to the pre-instrument process start state in Espresso
* unlike Appium UIA2 driver case. We want to disable/enable the animation scale only in an appium espresso session as possible.
* @param isEnabled
*/
async setWindowAnimationState(isEnabled) {
const isAnimationOn = await this.adb.isAnimationOn();
const shouldDisableAnimation = !isEnabled && isAnimationOn;
const shouldEnableAnimation = isEnabled && !isAnimationOn;
if (shouldDisableAnimation) {
this.log.debug('Disabling window animation as "disableWindowAnimation" capability is set to true/fallback to default value "true"');
await this.settingsApp.setAnimationState(false);
this.wasAnimationEnabled = true;
}
else if (shouldEnableAnimation) {
this.log.debug('Enabling window animation as "disableWindowAnimation" capability is set to false');
await this.settingsApp.setAnimationState(true);
this.wasAnimationEnabled = false;
}
else {
this.log.debug(`Window animation is already ${isEnabled ? 'enabled' : 'disabled'}`);
}
}
async initWebview() {
const viewName = this.defaultWebviewName();
const timeout = this.opts.autoWebviewTimeout || 2000;
this.log.info(`Setting webview to context '${viewName}' with timeout ${timeout}ms`);
await (0, asyncbox_1.retryInterval)(timeout / 500, 500, this.setContext.bind(this), viewName);
}
async addDeviceInfoToCaps() {
const { apiVersion, platformVersion, manufacturer, model, realDisplaySize, displayDensity, } = await this.mobileGetDeviceInfo();
this.caps.deviceApiLevel = parseInt(apiVersion, 10);
this.caps.platformVersion = platformVersion;
this.caps.deviceScreenSize = realDisplaySize;
this.caps.deviceScreenDensity = displayDensity;
this.caps.deviceModel = model;
this.caps.deviceManufacturer = manufacturer;
}
initEspressoServer() {
// now that we have package and activity, we can create an instance of
// espresso with the appropriate data
this.espresso = new espresso_runner_1.EspressoRunner(this.log, {
// @ts-ignore TODO: Is .host a legacy property?
host: this.opts.remoteAdbHost || this.opts.host || '127.0.0.1',
systemPort: this.opts.systemPort,
devicePort: DEVICE_PORT,
adb: this.adb,
apk: this.opts.app,
tmpDir: this.opts.tmpDir,
appPackage: this.opts.appPackage,
appActivity: this.opts.appActivity,
forceEspressoRebuild: !!this.opts.forceEspressoRebuild,
espressoBuildConfig: this.opts.espressoBuildConfig,
showGradleLog: !!this.opts.showGradleLog,
serverLaunchTimeout: this.opts.espressoServerLaunchTimeout,
androidInstallTimeout: this.opts.androidInstallTimeout,
skipServerInstallation: this.opts.skipServerInstallation,
useKeystore: this.opts.useKeystore,
keystorePath: this.opts.keystorePath,
keystorePassword: this.opts.keystorePassword,
keyAlias: this.opts.keyAlias,
keyPassword: this.opts.keyPassword,
disableSuppressAccessibilityService: this.opts.disableSuppressAccessibilityService,
reqBasePath: this.basePath,
});
this.proxyReqRes = this.espresso.proxyReqRes.bind(this.espresso);
this.proxyCommand = this.espresso.proxyCommand.bind(this.espresso);
}
async initAUT() {
// Uninstall any uninstallOtherPackages which were specified in caps
if (this.opts.uninstallOtherPackages) {
await this.uninstallOtherPackages(appium_android_driver_1.utils.parseArray(this.opts.uninstallOtherPackages), [io_appium_settings_1.SETTINGS_HELPER_ID, espresso_runner_1.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();
}
}
if (!this.opts.skipUninstall) {
await this.adb.uninstallApk(this.opts.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(io_appium_settings_1.SETTINGS_HELPER_ID, espresso_runner_1.TEST_APK_PKG);
}
catch (e) {
this.log.warn(`Cannot add server packages to the Doze whitelist. Original error: ` +
(e.stderr || e.message));
}
}
}
async deleteSession() {
this.log.debug('Deleting espresso session');
const screenRecordingStopTasks = [async () => {
if (!lodash_1.default.isEmpty(this._screenRecordingProperties)) {
await this.stopRecordingScreen();
}
}, async () => {
if (await this.mobileIsMediaProjectionRecordingRunning()) {
await this.mobileStopMediaProjectionRecording();
}
}, async () => {
if (!lodash_1.default.isEmpty(this._screenStreamingProps)) {
await this.mobileStopScreenStreaming();
}
}];
if (this.espresso) {
if (this.jwpProxyActive) {
await this.espresso.deleteSession();
}
// @ts-ignore This is ok
this.espresso = null;
}
this.jwpProxyActive = false;
if (this.adb) {
await bluebird_1.default.all(screenRecordingStopTasks.map((task) => {
(async () => {
try {
await task();
}
catch { }
})();
}));
if (this.wasAnimationEnabled) {
try {
await this.settingsApp.setAnimationState(true);
}
catch (err) {
this.log.warn(`Unable to reset animation: ${err.message}`);
}
}
if (this._originalIme) {
try {
await this.adb.setIME(this._originalIme);
}
catch (e) {
this.log.warn(`Cannot restore the original IME: ${e.message}`);
}
}
if (!this.isChromeSession && this.opts.appPackage && !this.opts.dontStopAppOnReset) {
await this.adb.forceStop(this.opts.appPackage);
}
if (this.opts.fullReset && !this.opts.skipUninstall && !this.appOnDevice) {
this.log.debug(`FULL_RESET set to 'true', Uninstalling '${this.opts.appPackage}'`);
await this.adb.uninstallApk(this.opts.appPackage);
}
if (await this.adb.getApiLevel() >= 28) { // Android P
this.log.info('Restoring hidden api policy to the device default configuration');
await this.adb.setDefaultHiddenApiPolicy(!!this.opts.ignoreHiddenApiPolicyError);
}
}
await super.deleteSession();
if (this.opts.systemPort) {
try {
await this.adb.removePortForward(this.opts.systemPort);
}
catch (error) {
this.log.warn(`Unable to remove port forward '${error.message}'`);
//Ignore, this block will also be called when we fall in catch block
// and before even port forward.
}
}
}
async onSettingsUpdate() {
// intentionally do nothing here, since commands.updateSettings proxies
// settings to the espresso server already
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
proxyActive(sessionId) {
// we always have an active proxy to the espresso server
return true;
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
canProxy(sessionId) {
// we can always proxy to the espresso server
return true;
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
getProxyAvoidList(sessionId) {
// we are maintaining two sets of NO_PROXY lists, one for chromedriver(CHROME_NO_PROXY)
// and one for Espresso(NO_PROXY), based on current context will return related NO_PROXY list
this.jwpProxyAvoid = lodash_1.default.isNil(this.chromedriver) ? NO_PROXY : CHROME_NO_PROXY;
if (this.opts.nativeWebScreenshot) {
this.jwpProxyAvoid = ([
...this.jwpProxyAvoid,
['GET', new RegExp('^/session/[^/]+/screenshot')]
]);
}
return this.jwpProxyAvoid;
}
get appOnDevice() {
return !this.opts.app && this.helpers.isPackageOrBundle(this.opts.appPackage);
}
}
exports.EspressoDriver = EspressoDriver;
EspressoDriver.newMethodMap = method_map_1.newMethodMap;
EspressoDriver.executeMethodMap = execute_method_map_1.executeMethodMap;
exports.default = EspressoDriver;
//# sourceMappingURL=driver.js.map