How to use adb.grantPermissions method in Appium Android Driver

Best JavaScript code snippet using appium-android-driver

Run Appium Android Driver automation tests on LambdaTest cloud grid

Perform automation testing on 3000+ real desktop and mobile devices online.

android-helpers.js

Source: android-helpers.js Github

copy
1import _ from 'lodash';
2import path from 'path';
3import { exec } from 'teen_process';
4import { retry, retryInterval, waitForCondition } from 'asyncbox';
5import logger from './logger';
6import { fs, util } from 'appium-support';
7import { path as settingsApkPath } from 'io.appium.settings';
8import Bootstrap from './bootstrap';
9import B from 'bluebird';
10import ADB from 'appium-adb';
11import {
12  default as unlocker, PIN_UNLOCK, PASSWORD_UNLOCK,
13  PATTERN_UNLOCK, FINGERPRINT_UNLOCK } from './unlock-helpers';
14import { EOL } from 'os';
15
16const PACKAGE_INSTALL_TIMEOUT = 90000; // milliseconds
17const CHROME_BROWSER_PACKAGE_ACTIVITY = {
18  chrome: {
19    pkg: 'com.android.chrome',
20    activity: 'com.google.android.apps.chrome.Main',
21  },
22  chromium: {
23    pkg: 'org.chromium.chrome.shell',
24    activity: '.ChromeShellActivity',
25  },
26  chromebeta: {
27    pkg: 'com.chrome.beta',
28    activity: 'com.google.android.apps.chrome.Main',
29  },
30  browser: {
31    pkg: 'com.android.browser',
32    activity: 'com.android.browser.BrowserActivity',
33  },
34  'chromium-browser': {
35    pkg: 'org.chromium.chrome',
36    activity: 'com.google.android.apps.chrome.Main',
37  },
38  'chromium-webview': {
39    pkg: 'org.chromium.webview_shell',
40    activity: 'org.chromium.webview_shell.WebViewBrowserActivity',
41  },
42  default: {
43    pkg: 'com.android.chrome',
44    activity: 'com.google.android.apps.chrome.Main',
45  },
46};
47const SETTINGS_HELPER_PKG_ID = 'io.appium.settings';
48const SETTINGS_HELPER_MAIN_ACTIVITY = '.Settings';
49const SETTINGS_HELPER_UNLOCK_ACTIVITY = '.Unlock';
50
51let helpers = {};
52
53helpers.createBaseADB = async function createBaseADB (opts = {}) {
54  // filter out any unwanted options sent in
55  // this list should be updated as ADB takes more arguments
56  const {
57    adbPort,
58    suppressKillServer,
59    remoteAdbHost,
60    clearDeviceLogsOnStart,
61    adbExecTimeout,
62    useKeystore,
63    keystorePath,
64    keystorePassword,
65    keyAlias,
66    keyPassword,
67    remoteAppsCacheLimit,
68    buildToolsVersion,
69  } = opts;
70  return await ADB.createADB({
71    adbPort,
72    suppressKillServer,
73    remoteAdbHost,
74    clearDeviceLogsOnStart,
75    adbExecTimeout,
76    useKeystore,
77    keystorePath,
78    keystorePassword,
79    keyAlias,
80    keyPassword,
81    remoteAppsCacheLimit,
82    buildToolsVersion,
83  });
84};
85
86helpers.getJavaVersion = async function getJavaVersion (logVersion = true) {
87  let stderr;
88  try {
89    ({stderr} = await exec('java', ['-version']));
90  } catch (e) {
91    throw new Error(`Could not get the Java version. Is Java installed? Original error: ${e.stderr}`);
92  }
93  const versionMatch = /(java|openjdk)\s+version.+?([0-9._]+)/i.exec(stderr);
94  if (!versionMatch) {
95    throw new Error(`Could not parse Java version. Is Java installed? Original output: ${stderr}`);
96  }
97  if (logVersion) {
98    logger.info(`Java version is: ${versionMatch[2]}`);
99  }
100  return versionMatch[2];
101};
102
103helpers.prepareEmulator = async function prepareEmulator (adb, opts) {
104  let {
105    avd,
106    avdArgs,
107    language,
108    locale,
109    avdLaunchTimeout,
110    avdReadyTimeout,
111  } = opts;
112  if (!avd) {
113    throw new Error('Cannot launch AVD without AVD name');
114  }
115  let avdName = avd.replace('@', '');
116  let runningAVD = await adb.getRunningAVD(avdName);
117  if (runningAVD !== null) {
118    if (avdArgs && avdArgs.toLowerCase().indexOf('-wipe-data') > -1) {
119      logger.debug(`Killing '${avdName}' because it needs to be wiped at start.`);
120      await adb.killEmulator(avdName);
121    } else {
122      logger.debug('Not launching AVD because it is already running.');
123      return;
124    }
125  }
126  avdArgs = this.prepareAVDArgs(opts, adb, avdArgs);
127  await adb.launchAVD(avd, avdArgs, language, locale, avdLaunchTimeout,
128                      avdReadyTimeout);
129};
130
131helpers.prepareAVDArgs = function prepareAVDArgs (opts, adb, avdArgs) {
132  let args = avdArgs ? [avdArgs] : [];
133  if (!_.isUndefined(opts.networkSpeed)) {
134    let networkSpeed = this.ensureNetworkSpeed(adb, opts.networkSpeed);
135    args.push('-netspeed', networkSpeed);
136  }
137  if (opts.isHeadless) {
138    args.push('-no-window');
139  }
140  return args.join(' ');
141};
142
143helpers.ensureNetworkSpeed = function ensureNetworkSpeed (adb, networkSpeed) {
144  if (_.values(adb.NETWORK_SPEED).indexOf(networkSpeed) !== -1) {
145    return networkSpeed;
146  }
147  logger.warn(`Wrong network speed param ${networkSpeed}, using default: full. Supported values: ${_.values(adb.NETWORK_SPEED)}`);
148  return adb.NETWORK_SPEED.FULL;
149};
150
151/**
152 * Set and ensure the locale name of the device under test.
153 *
154 * @param {Object} adb - The adb module instance.
155 * @param {string} language - Language. The language field is case insensitive, but Locale always canonicalizes to lower case.
156 *                            format: [a-zA-Z]{2,8}. e.g. en, ja : https://developer.android.com/reference/java/util/Locale.html
157 * @param {string} country - Country. The country (region) field is case insensitive, but Locale always canonicalizes to upper case.
158 *                            format: [a-zA-Z]{2} | [0-9]{3}. e.g. US, JP : https://developer.android.com/reference/java/util/Locale.html
159 * @param {?string} script - Script. The script field is case insensitive but Locale always canonicalizes to title case.
160 *                            format: [a-zA-Z]{4}. e.g. Hans in zh-Hans-CN : https://developer.android.com/reference/java/util/Locale.html
161 * @throws {Error} If it failed to set locale properly
162 */
163helpers.ensureDeviceLocale = async function ensureDeviceLocale (adb, language, country, script = null) {
164  if (!_.isString(language) && !_.isString(country)) {
165    logger.warn(`setDeviceLanguageCountry requires language or country.`);
166    logger.warn(`Got language: '${language}' and country: '${country}'`);
167    return;
168  }
169
170  await adb.setDeviceLanguageCountry(language, country, script);
171
172  if (!await adb.ensureCurrentLocale(language, country, script)) {
173    const message = script ? `language: ${language}, country: ${country} and script: ${script}` : `language: ${language} and country: ${country}`;
174    throw new Error(`Failed to set ${message}`);
175  }
176};
177
178helpers.getDeviceInfoFromCaps = async function getDeviceInfoFromCaps (opts = {}) {
179  // we can create a throwaway ADB instance here, so there is no dependency
180  // on instantiating on earlier (at this point, we have no udid)
181  // we can only use this ADB object for commands that would not be confused
182  // if multiple devices are connected
183  const adb = await helpers.createBaseADB(opts);
184  let udid = opts.udid;
185  let emPort = null;
186
187  // a specific avd name was given. try to initialize with that
188  if (opts.avd) {
189    await helpers.prepareEmulator(adb, opts);
190    udid = adb.curDeviceId;
191    emPort = adb.emulatorPort;
192  } else {
193    // no avd given. lets try whatever's plugged in devices/emulators
194    logger.info('Retrieving device list');
195    let devices = await adb.getDevicesWithRetry();
196
197    // udid was given, lets try to init with that device
198    if (udid) {
199      if (!_.includes(_.map(devices, 'udid'), udid)) {
200        logger.errorAndThrow(`Device ${udid} was not in the list of connected devices`);
201      }
202      emPort = adb.getPortFromEmulatorString(udid);
203    } else if (opts.platformVersion) {
204      opts.platformVersion = `${opts.platformVersion}`.trim();
205
206      // a platform version was given. lets try to find a device with the same os
207      const platformVersion = util.coerceVersion(opts.platformVersion, false);
208      if (!platformVersion) {
209        logger.errorAndThrow(`The provided platform version value '${platformVersion}' ` +
210          `cannot be coerced to a valid version number`);
211      }
212      logger.info(`Looking for a device with Android '${opts.platformVersion}'`);
213
214      // in case we fail to find something, give the user a useful log that has
215      // the device udids and os versions so they know what's available
216      const availDevices = [];
217      let partialMatchCandidate = null;
218      const extractVersionDigits = (versionStr) => {
219        const match = /(\d+)\.?(\d+)?/.exec(versionStr);
220        return match ? match.slice(1) : [];
221      };
222      const [majorPlatformVersion, minorPlatformVersion] = extractVersionDigits(platformVersion);
223      // first try started devices/emulators
224      for (const device of devices) {
225        // direct adb calls to the specific device
226        await adb.setDeviceId(device.udid);
227        const rawDeviceOS = await adb.getPlatformVersion();
228        availDevices.push(`${device.udid} (${rawDeviceOS})`);
229        const deviceOS = util.coerceVersion(rawDeviceOS, false);
230        if (!deviceOS) {
231          continue;
232        }
233
234        if (util.compareVersions(deviceOS, '==', platformVersion)) {
235          // Got an exact match - proceed immediately
236          udid = device.udid;
237          break;
238        }
239
240        const [majorDeviceVersion, minorDeviceVersion] = extractVersionDigits(deviceOS);
241        if ((!opts.platformVersion.includes('.') && majorPlatformVersion === majorDeviceVersion)
242          || (majorPlatformVersion === majorDeviceVersion && minorPlatformVersion === minorDeviceVersion)) {
243          // Got a partial match - make sure we consider the most recent
244          // device version available on the host system
245          if (partialMatchCandidate
246              && util.compareVersions(deviceOS, '>', _.values(partialMatchCandidate)[0])
247              || !partialMatchCandidate) {
248            partialMatchCandidate = {[device.udid]: deviceOS};
249          }
250        }
251      }
252      if (!udid && partialMatchCandidate) {
253        udid = _.keys(partialMatchCandidate)[0];
254        await adb.setDeviceId(udid);
255      }
256
257      if (!udid) {
258        // we couldn't find anything! quit
259        logger.errorAndThrow(`Unable to find an active device or emulator ` +
260          `with OS ${opts.platformVersion}. The following are available: ` +
261          availDevices.join(', '));
262      }
263
264      emPort = adb.getPortFromEmulatorString(udid);
265    } else {
266      // a udid was not given, grab the first device we see
267      udid = devices[0].udid;
268      emPort = adb.getPortFromEmulatorString(udid);
269    }
270  }
271
272  logger.info(`Using device: ${udid}`);
273  return {udid, emPort};
274};
275
276// returns a new adb instance with deviceId set
277helpers.createADB = async function createADB (opts = {}) {
278  const {udid, emPort} = opts;
279  const adb = await helpers.createBaseADB(opts);
280  adb.setDeviceId(udid);
281  if (emPort) {
282    adb.setEmulatorPort(emPort);
283  }
284
285  return adb;
286};
287
288helpers.validatePackageActivityNames = function validatePackageActivityNames (opts) {
289  for (const key of ['appPackage', 'appActivity', 'appWaitPackage', 'appWaitActivity']) {
290    const name = opts[key];
291    if (!name) {
292      continue;
293    }
294
295    const match = /([^\w.*,])+/.exec(name);
296    if (!match) {
297      continue;
298    }
299
300    logger.warn(`Capability '${key}' is expected to only include latin letters, digits, underscore, dot, comma and asterisk characters.`);
301    logger.warn(`Current value '${name}' has non-matching character at index ${match.index}: '${name.substring(0, match.index + 1)}'`);
302  }
303};
304
305helpers.getLaunchInfo = async function getLaunchInfo (adb, opts) {
306  let {app, appPackage, appActivity, appWaitPackage, appWaitActivity} = opts;
307  if (!app) {
308    logger.warn('No app sent in, not parsing package/activity');
309    return;
310  }
311
312  this.validatePackageActivityNames(opts);
313
314  if (appPackage && appActivity) {
315    return;
316  }
317
318  logger.debug('Parsing package and activity from app manifest');
319  let {apkPackage, apkActivity} =
320    await adb.packageAndLaunchActivityFromManifest(app);
321  if (apkPackage && !appPackage) {
322    appPackage = apkPackage;
323  }
324  if (!appWaitPackage) {
325    appWaitPackage = appPackage;
326  }
327  if (apkActivity && !appActivity) {
328    appActivity = apkActivity;
329  }
330  if (!appWaitActivity) {
331    appWaitActivity = appActivity;
332  }
333  logger.debug(`Parsed package and activity are: ${apkPackage}/${apkActivity}`);
334  return {appPackage, appWaitPackage, appActivity, appWaitActivity};
335};
336
337helpers.resetApp = async function resetApp (adb, opts = {}) {
338  const {
339    app,
340    appPackage,
341    fastReset,
342    fullReset,
343    androidInstallTimeout = PACKAGE_INSTALL_TIMEOUT,
344    autoGrantPermissions,
345    allowTestPackages
346  } = opts;
347
348  if (!appPackage) {
349    throw new Error("'appPackage' option is required");
350  }
351
352  const isInstalled = await adb.isAppInstalled(appPackage);
353
354  if (isInstalled) {
355    try {
356      await adb.forceStop(appPackage);
357    } catch (ign) {}
358    // fullReset has priority over fastReset
359    if (!fullReset && fastReset) {
360      const output = await adb.clear(appPackage);
361      if (_.isString(output) && output.toLowerCase().includes('failed')) {
362        throw new Error(`Cannot clear the application data of '${appPackage}'. Original error: ${output}`);
363      }
364      // executing `shell pm clear` resets previously assigned application permissions as well
365      if (autoGrantPermissions) {
366        try {
367          await adb.grantAllPermissions(appPackage);
368        } catch (error) {
369          logger.error(`Unable to grant permissions requested. Original error: ${error.message}`);
370        }
371      }
372      logger.debug(`Performed fast reset on the installed '${appPackage}' application (stop and clear)`);
373      return;
374    }
375  }
376
377  if (!app) {
378    throw new Error("'app' option is required for reinstall");
379  }
380
381  logger.debug(`Running full reset on '${appPackage}' (reinstall)`);
382  if (isInstalled) {
383    await adb.uninstallApk(appPackage);
384  }
385  await adb.install(app, {
386    grantPermissions: autoGrantPermissions,
387    timeout: androidInstallTimeout,
388    allowTestPackages,
389  });
390};
391
392helpers.installApk = async function installApk (adb, opts = {}) {
393  const {
394    app,
395    appPackage,
396    fastReset,
397    fullReset,
398    androidInstallTimeout = PACKAGE_INSTALL_TIMEOUT,
399    autoGrantPermissions,
400    allowTestPackages,
401    enforceAppInstall,
402  } = opts;
403
404  if (!app || !appPackage) {
405    throw new Error("'app' and 'appPackage' options are required");
406  }
407
408  if (fullReset) {
409    await this.resetApp(adb, opts);
410    return;
411  }
412
413  const {
414    appState,
415    wasUninstalled
416  } = await adb.installOrUpgrade(app, appPackage, {
417    grantPermissions: autoGrantPermissions,
418    timeout: androidInstallTimeout,
419    allowTestPackages,
420    enforceCurrentBuild: enforceAppInstall,
421  });
422
423  // There is no need to reset the newly installed app
424  const isInstalledOverExistingApp = !wasUninstalled
425    && appState !== adb.APP_INSTALL_STATE.NOT_INSTALLED;
426  if (fastReset && isInstalledOverExistingApp) {
427    logger.info(`Performing fast reset on '${appPackage}'`);
428    await this.resetApp(adb, opts);
429  }
430};
431
432/**
433 * Installs an array of apks
434 * @param {ADB} adb Instance of Appium ADB object
435 * @param {Object} opts Opts defined in driver.js
436 */
437helpers.installOtherApks = async function installOtherApks (otherApps, adb, opts) {
438  let {
439    androidInstallTimeout = PACKAGE_INSTALL_TIMEOUT,
440    autoGrantPermissions,
441    allowTestPackages
442  } = opts;
443
444  // Install all of the APK's asynchronously
445  await B.all(otherApps.map((otherApp) => {
446    logger.debug(`Installing app: ${otherApp}`);
447    return adb.installOrUpgrade(otherApp, null, {
448      grantPermissions: autoGrantPermissions,
449      timeout: androidInstallTimeout,
450      allowTestPackages,
451    });
452  }));
453};
454
455/**
456 * Uninstall an array of packages
457 * @param {ADB} adb Instance of Appium ADB object
458 * @param {Array<string>} appPackages An array of package names to uninstall. If this includes `'*'`, uninstall all of 3rd party apps
459 * @param {Array<string>} filterPackages An array of packages does not uninstall when `*` is provided as `appPackages`
460 */
461helpers.uninstallOtherPackages = async function uninstallOtherPackages (adb, appPackages, filterPackages = []) {
462  if (appPackages.includes('*')) {
463    logger.debug('Uninstall third party packages');
464    appPackages = await this.getThirdPartyPackages(adb, filterPackages);
465  }
466
467  logger.debug(`Uninstalling packages: ${appPackages}`);
468  await B.all(appPackages.map((appPackage) => adb.uninstallApk(appPackage)));
469};
470
471/**
472 * Get third party packages filtered with `filterPackages`
473 * @param {ADB} adb Instance of Appium ADB object
474 * @param {Array<string>} filterPackages An array of packages does not uninstall when `*` is provided as `appPackages`
475 * @returns {Array<string>} An array of installed third pary packages
476 */
477helpers.getThirdPartyPackages = async function getThirdPartyPackages (adb, filterPackages = []) {
478  try {
479    const packagesString = await adb.shell(['pm', 'list', 'packages', '-3']);
480    const appPackagesArray = packagesString.trim().replace(/package:/g, '').split(EOL);
481    logger.debug(`'${appPackagesArray}' filtered with '${filterPackages}'`);
482    return _.difference(appPackagesArray, filterPackages);
483  } catch (err) {
484    logger.warn(`Unable to get packages with 'adb shell pm list packages -3': ${err.message}`);
485    return [];
486  }
487};
488
489helpers.initUnicodeKeyboard = async function initUnicodeKeyboard (adb) {
490  logger.debug('Enabling Unicode keyboard support');
491
492  // get the default IME so we can return back to it later if we want
493  let defaultIME = await adb.defaultIME();
494
495  logger.debug(`Unsetting previous IME ${defaultIME}`);
496  const appiumIME = `${SETTINGS_HELPER_PKG_ID}/.UnicodeIME`;
497  logger.debug(`Setting IME to '${appiumIME}'`);
498  await adb.enableIME(appiumIME);
499  await adb.setIME(appiumIME);
500  return defaultIME;
501};
502
503helpers.setMockLocationApp = async function setMockLocationApp (adb, app) {
504  try {
505    if (await adb.getApiLevel() < 23) {
506      await adb.shell(['settings', 'put', 'secure', 'mock_location', '1']);
507    } else {
508      await adb.shell(['appops', 'set', app, 'android:mock_location', 'allow']);
509    }
510  } catch (err) {
511    logger.warn(`Unable to set mock location for app '${app}': ${err.message}`);
512  }
513};
514
515helpers.installHelperApp = async function installHelperApp (adb, apkPath, packageId) {
516  // Sometimes adb push or adb instal take more time than expected to install an app
517  // e.g. https://github.com/appium/io.appium.settings/issues/40#issuecomment-476593174
518  await retry(2, async function retryInstallHelperApp () {
519    await adb.installOrUpgrade(apkPath, packageId, {grantPermissions: true});
520  });
521};
522
523/**
524 * Pushes and installs io.appium.settings app.
525 * Throws an error if the setting app is required
526 *
527 * @param {Adb} adb - The adb module instance.
528 * @param {boolean} throwError[false] - Whether throw error or not
529 * @throws {Error} If throwError is true and something happens in installation step
530 */
531helpers.pushSettingsApp = async function pushSettingsApp (adb, throwError = false) {
532  logger.debug('Pushing settings apk to device...');
533
534  try {
535    await helpers.installHelperApp(adb, settingsApkPath, SETTINGS_HELPER_PKG_ID, throwError);
536  } catch (err) {
537    if (throwError) {
538      throw err;
539    }
540
541    logger.warn(`Ignored error while installing '${settingsApkPath}': ` +
542                `'${err.message}'. Features that rely on this helper ` +
543                'require the apk such as toggle WiFi and getting location ' +
544                'will raise an error if you try to use them.');
545  }
546
547  // Reinstall will stop the settings helper process anyway, so
548  // there is no need to continue if the application is still running
549  if (await adb.processExists(SETTINGS_HELPER_PKG_ID)) {
550    logger.debug(`${SETTINGS_HELPER_PKG_ID} is already running. ` +
551      `There is no need to reset its permissions.`);
552    return;
553  }
554
555  if (await adb.getApiLevel() <= 23) { // Android 6- devices should have granted permissions
556    // https://github.com/appium/appium/pull/11640#issuecomment-438260477
557    logger.info('Granting android.permission.SET_ANIMATION_SCALE, CHANGE_CONFIGURATION, ACCESS_FINE_LOCATION by pm grant');
558    await adb.grantPermissions(SETTINGS_HELPER_PKG_ID, [
559      'android.permission.SET_ANIMATION_SCALE',
560      'android.permission.CHANGE_CONFIGURATION',
561      'android.permission.ACCESS_FINE_LOCATION'
562    ]);
563  }
564
565  // launch io.appium.settings app due to settings failing to be set
566  // if the app is not launched prior to start the session on android 7+
567  // see https://github.com/appium/appium/issues/8957
568  try {
569    await adb.startApp({
570      pkg: SETTINGS_HELPER_PKG_ID,
571      activity: SETTINGS_HELPER_MAIN_ACTIVITY,
572      action: 'android.intent.action.MAIN',
573      category: 'android.intent.category.LAUNCHER',
574      stopApp: false,
575      waitForLaunch: false,
576    });
577
578    await waitForCondition(async () => await adb.processExists(SETTINGS_HELPER_PKG_ID), {
579      waitMs: 5000,
580      intervalMs: 300,
581    });
582  } catch (err) {
583    const message = `Failed to launch Appium Settings app: ${err.message}`;
584    err.message = message;
585    logger.warn(message);
586    if (throwError) {
587      throw err;
588    }
589  }
590};
591
592/**
593 * Extracts string.xml and converts it to string.json and pushes
594 * it to /data/local/tmp/string.json on for use of bootstrap
595 * If app is not present to extract string.xml it deletes remote strings.json
596 * If app does not have strings.xml we push an empty json object to remote
597 *
598 * @param {?string} language - Language abbreviation, for example 'fr'. The default language
599 * is used if this argument is not defined.
600 * @param {Object} adb - The adb module instance.
601 * @param {Object} opts - Driver options dictionary.
602 * @returns {Object} The dictionary, where string resource identifiers are keys
603 * along with their corresponding values for the given language or an empty object
604 * if no matching resources were extracted.
605 */
606helpers.pushStrings = async function pushStrings (language, adb, opts) {
607  const remoteDir = '/data/local/tmp';
608  const stringsJson = 'strings.json';
609  const remoteFile = path.posix.resolve(remoteDir, stringsJson);
610
611  // clean up remote string.json if present
612  await adb.rimraf(remoteFile);
613
614  let app;
615  try {
616    app = opts.app || await adb.pullApk(opts.appPackage, opts.tmpDir);
617  } catch (err) {
618    logger.info(`Failed to pull an apk from '${opts.appPackage}' to '${opts.tmpDir}'. Original error: ${err.message}`);
619  }
620
621  if (_.isEmpty(opts.appPackage) || !(await fs.exists(app))) {
622    logger.debug(`No app or package specified. Returning empty strings`);
623    return {};
624  }
625
626  const stringsTmpDir = path.resolve(opts.tmpDir, opts.appPackage);
627  try {
628    logger.debug('Extracting strings from apk', app, language, stringsTmpDir);
629    const {apkStrings, localPath} = await adb.extractStringsFromApk(app, language, stringsTmpDir);
630    await adb.push(localPath, remoteDir);
631    return apkStrings;
632  } catch (err) {
633    logger.warn(`Could not get strings, continuing anyway. Original error: ${err.message}`);
634    await adb.shell('echo', [`'{}' > ${remoteFile}`]);
635  } finally {
636    await fs.rimraf(stringsTmpDir);
637  }
638  return {};
639};
640
641helpers.unlockWithUIAutomation = async function unlockWithUIAutomation (driver, adb, unlockCapabilities) {
642  let unlockType = unlockCapabilities.unlockType;
643  if (!unlocker.isValidUnlockType(unlockType)) {
644    throw new Error(`Invalid unlock type ${unlockType}`);
645  }
646  let unlockKey = unlockCapabilities.unlockKey;
647  if (!unlocker.isValidKey(unlockType, unlockKey)) {
648    throw new Error(`Missing unlockKey ${unlockKey} capability for unlockType ${unlockType}`);
649  }
650  const unlockMethod = {
651    [PIN_UNLOCK]: unlocker.pinUnlock,
652    [PASSWORD_UNLOCK]: unlocker.passwordUnlock,
653    [PATTERN_UNLOCK]: unlocker.patternUnlock,
654    [FINGERPRINT_UNLOCK]: unlocker.fingerprintUnlock
655  }[unlockType];
656  await unlockMethod(adb, driver, unlockCapabilities);
657};
658
659helpers.unlockWithHelperApp = async function unlockWithHelperApp (adb) {
660  logger.info('Unlocking screen');
661
662  // Unlock succeed with a couple of retries.
663  let firstRun = true;
664  await retry(3, async function launchHelper () {
665    // To reduce a time to call adb.isScreenLocked() since `adb shell dumpsys window` is easy to hang adb commands
666    if (firstRun) {
667      firstRun = false;
668    } else {
669      try {
670        if (!(await adb.isScreenLocked())) {
671          return;
672        }
673      } catch (e) {
674        logger.warn(`Error in isScreenLocked: ${e.message}`);
675        logger.warn('"adb shell dumpsys window" command has timed out.');
676        logger.warn('The reason of this timeout is the delayed adb response. Resetting adb server can improve it.');
677      }
678    }
679
680    logger.info(`Launching ${SETTINGS_HELPER_UNLOCK_ACTIVITY}`);
681    await adb.shell([
682      'am', 'start',
683      '-n', `${SETTINGS_HELPER_PKG_ID}/${SETTINGS_HELPER_UNLOCK_ACTIVITY}`,
684      '-c', 'android.intent.category.LAUNCHER',
685      '-a', 'android.intent.action.MAIN',
686      '-f', '0x10200000',
687    ]);
688    await B.delay(1000);
689  });
690};
691
692helpers.unlock = async function unlock (driver, adb, capabilities) {
693  if (!(await adb.isScreenLocked())) {
694    logger.info('Screen already unlocked, doing nothing');
695    return;
696  }
697
698  logger.debug('Screen is locked, trying to unlock');
699  if (_.isUndefined(capabilities.unlockType)) {
700    logger.warn('Using app unlock, this is going to be deprecated!');
701    await helpers.unlockWithHelperApp(adb);
702  } else {
703    await helpers.unlockWithUIAutomation(driver, adb, {unlockType: capabilities.unlockType, unlockKey: capabilities.unlockKey});
704    await helpers.verifyUnlock(adb);
705  }
706};
707
708helpers.verifyUnlock = async function verifyUnlock (adb) {
709  await retryInterval(2, 1000, async () => {
710    if (await adb.isScreenLocked()) {
711      throw new Error('Screen did not unlock successfully, retrying');
712    }
713    logger.debug('Screen unlocked successfully');
714  });
715};
716
717helpers.initDevice = async function initDevice (adb, opts) {
718  if (opts.skipDeviceInitialization) {
719    logger.info(`'skipDeviceInitialization' is set. Skipping device initialization.`);
720  } else {
721    await adb.waitForDevice();
722    // pushSettingsApp required before calling ensureDeviceLocale for API Level 24+
723
724    // Some feature such as location/wifi are not necessary for all users,
725    // but they require the settings app. So, try to configure it while Appium
726    // does not throw error even if they fail.
727    const shouldThrowError = opts.language
728                          || opts.locale
729                          || opts.localeScript
730                          || opts.unicodeKeyboard
731                          || opts.disableWindowAnimation
732                          || !opts.skipUnlock;
733    await helpers.pushSettingsApp(adb, shouldThrowError);
734  }
735
736  if (!opts.avd) {
737    await helpers.setMockLocationApp(adb, SETTINGS_HELPER_PKG_ID);
738  }
739
740  if (opts.language || opts.locale) {
741    await helpers.ensureDeviceLocale(adb, opts.language, opts.locale, opts.localeScript);
742  }
743
744  if (opts.skipLogcatCapture) {
745    logger.info(`'skipLogcatCapture' is set. Skipping starting logcat capture.`);
746  } else {
747    await adb.startLogcat();
748  }
749
750  if (opts.unicodeKeyboard) {
751    return await helpers.initUnicodeKeyboard(adb);
752  }
753};
754
755helpers.removeNullProperties = function removeNullProperties (obj) {
756  for (let key of _.keys(obj)) {
757    if (_.isNull(obj[key]) || _.isUndefined(obj[key])) {
758      delete obj[key];
759    }
760  }
761};
762
763helpers.truncateDecimals = function truncateDecimals (number, digits) {
764  let multiplier = Math.pow(10, digits),
765      adjustedNum = number * multiplier,
766      truncatedNum = Math[adjustedNum < 0 ? 'ceil' : 'floor'](adjustedNum);
767
768  return truncatedNum / multiplier;
769};
770
771helpers.isChromeBrowser = function isChromeBrowser (browser) {
772  return _.includes(Object.keys(CHROME_BROWSER_PACKAGE_ACTIVITY), (browser || '').toLowerCase());
773};
774
775helpers.getChromePkg = function getChromePkg (browser) {
776  return CHROME_BROWSER_PACKAGE_ACTIVITY[browser.toLowerCase()] || CHROME_BROWSER_PACKAGE_ACTIVITY.default;
777};
778
779helpers.removeAllSessionWebSocketHandlers = async function removeAllSessionWebSocketHandlers (server, sessionId) {
780  if (!server || !_.isFunction(server.getWebSocketHandlers)) {
781    return;
782  }
783
784  const activeHandlers = await server.getWebSocketHandlers(sessionId);
785  for (const pathname of _.keys(activeHandlers)) {
786    await server.removeWebSocketHandler(pathname);
787  }
788};
789
790/**
791 * Takes a desired capability and tries to JSON.parse it as an array,
792 * and either returns the parsed array or a singleton array.
793 *
794 * @param {any} cap A desired capability
795 */
796helpers.parseArray = function parseArray (cap) {
797  let parsedCaps;
798  try {
799    parsedCaps = JSON.parse(cap);
800  } catch (ign) { }
801
802  if (_.isArray(parsedCaps)) {
803    return parsedCaps;
804  } else if (_.isString(cap)) {
805    return [cap];
806  }
807
808  throw new Error(`must provide a string or JSON Array; received ${cap}`);
809};
810
811/**
812 * Validate desired capabilities. Returns true if capability is valid
813 *
814 * @param {*} cap A desired capability
815 * @return {boolean} Returns true if the capability is valid
816 * @throws {Error} If the caps has invalid capability
817 */
818helpers.validateDesiredCaps = function validateDesiredCaps (caps) {
819  if (caps.browserName) {
820    if (caps.app) {
821      // warn if the capabilities have both `app` and `browser, although this is common with selenium grid
822      logger.warn(`The desired capabilities should generally not include both an 'app' and a 'browserName'`);
823    }
824    if (caps.appPackage) {
825      logger.errorAndThrow(`The desired should not include both of an 'appPackage' and a 'browserName'`);
826    }
827  }
828
829  if (caps.uninstallOtherPackages) {
830    try {
831      this.parseArray(caps.uninstallOtherPackages);
832    } catch (e) {
833      logger.errorAndThrow(`Could not parse "uninstallOtherPackages" capability: ${e.message}`);
834    }
835  }
836
837  return true;
838};
839
840helpers.bootstrap = Bootstrap;
841helpers.unlocker = unlocker;
842
843export { helpers, SETTINGS_HELPER_PKG_ID };
844export default helpers;
845
Full Screen

Accelerate Your Automation Test Cycles With LambdaTest

Leverage LambdaTest’s cloud-based platform to execute your automation tests in parallel and trim down your test execution time significantly. Your first 100 automation testing minutes are on us.

Try LambdaTest

Run JavaScript Tests on LambdaTest Cloud Grid

Execute automation tests with Appium Android Driver on a cloud-based Grid of 3000+ real browsers and operating systems for both web and mobile applications.

Test now for Free
LambdaTestX

We use cookies to give you the best experience. Cookies help to provide a more personalized experience and relevant advertising for you, and web analytics for us. Learn More in our Cookies policy, Privacy & Terms of service

Allow Cookie
Sarah

I hope you find the best code examples for your project.

If you want to accelerate automated browser testing, try LambdaTest. Your first 100 automation testing minutes are FREE.

Sarah Elson (Product & Growth Lead)