How to use this.bootstrap.shutdown 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.

driver.js

Source: driver.js Github

copy
1import { BaseDriver, DeviceSettings } from 'appium-base-driver';
2import Chromedriver from 'appium-chromedriver';
3import desiredConstraints from './desired-caps';
4import commands from './commands/index';
5import { setupNewChromedriver } from './commands/context';
6import helpers from './android-helpers';
7import { CHROMIUM_WIN } from './webview-helpers';
8import log from './logger';
9import _ from 'lodash';
10import { DEFAULT_ADB_PORT } from 'appium-adb';
11import { fs, tempDir, util } from 'appium-support';
12import { retryInterval } from 'asyncbox';
13import { SharedPrefsBuilder } from 'shared-preferences-builder';
14
15const APP_EXTENSION = '.apk';
16const DEVICE_PORT = 4724;
17
18// This is a set of methods and paths that we never want to proxy to
19// Chromedriver
20const NO_PROXY = [
21  ['POST', new RegExp('^/session/[^/]+/context')],
22  ['GET', new RegExp('^/session/[^/]+/context')],
23  ['POST', new RegExp('^/session/[^/]+/appium')],
24  ['GET', new RegExp('^/session/[^/]+/appium')],
25  ['POST', new RegExp('^/session/[^/]+/touch/perform')],
26  ['POST', new RegExp('^/session/[^/]+/touch/multi/perform')],
27  ['POST', new RegExp('^/session/[^/]+/orientation')],
28  ['GET', new RegExp('^/session/[^/]+/orientation')],
29];
30
31class AndroidDriver extends BaseDriver {
32  constructor (opts = {}, shouldValidateCaps = true) {
33    super(opts, shouldValidateCaps);
34
35    this.locatorStrategies = [
36      'xpath',
37      'id',
38      'class name',
39      'accessibility id',
40      '-android uiautomator'
41    ];
42    this.desiredCapConstraints = desiredConstraints;
43    this.sessionChromedrivers = {};
44    this.jwpProxyActive = false;
45    this.jwpProxyAvoid = _.clone(NO_PROXY);
46    this.settings = new DeviceSettings({ignoreUnimportantViews: false},
47                                       this.onSettingsUpdate.bind(this));
48    this.chromedriver = null;
49    this.apkStrings = {};
50    this.bootstrapPort = opts.bootstrapPort || DEVICE_PORT;
51    this.unlocker = helpers.unlocker;
52
53    for (let [cmd, fn] of _.toPairs(commands)) {
54      AndroidDriver.prototype[cmd] = fn;
55    }
56  }
57
58  async createSession (...args) {
59    // the whole createSession flow is surrounded in a try-catch statement
60    // if creating a session fails at any point, we teardown everything we
61    // set up before throwing the error.
62    try {
63      let [sessionId, caps] = await super.createSession(...args);
64
65      let serverDetails = {platform: 'LINUX',
66                           webStorageEnabled: false,
67                           takesScreenshot: true,
68                           javascriptEnabled: true,
69                           databaseEnabled: false,
70                           networkConnectionEnabled: true,
71                           locationContextEnabled: false,
72                           warnings: {},
73                           desired: this.caps};
74
75      this.caps = Object.assign(serverDetails, this.caps);
76
77      // assigning defaults
78      let defaultOpts = {
79        action: "android.intent.action.MAIN",
80        category: "android.intent.category.LAUNCHER",
81        flags: "0x10200000",
82        disableAndroidWatchers: false,
83        tmpDir: await tempDir.staticDir(),
84        fullReset: false,
85        autoLaunch: true,
86        adbPort: DEFAULT_ADB_PORT,
87        androidInstallTimeout: 90000
88      };
89      _.defaults(this.opts, defaultOpts);
90      if (!this.opts.javaVersion) {
91        this.opts.javaVersion = await helpers.getJavaVersion();
92      }
93      this.useUnlockHelperApp = _.isUndefined(this.caps.unlockType);
94
95      // not user visible via caps
96      if (this.opts.noReset === true) {
97        this.opts.fullReset = false;
98      }
99      if (this.opts.fullReset === true) {
100        this.opts.noReset = false;
101      }
102      this.opts.fastReset = !this.opts.fullReset && !this.opts.noReset;
103      this.opts.skipUninstall = this.opts.fastReset || this.opts.noReset;
104
105      this.curContext = this.defaultContextName();
106
107      if (this.isChromeSession) {
108        log.info("We're going to run a Chrome-based session");
109        let {pkg, activity} = helpers.getChromePkg(this.opts.browserName);
110        this.opts.appPackage = pkg;
111        this.opts.appActivity = activity;
112        log.info(`Chrome-type package and activity are ${pkg} and ${activity}`);
113      }
114
115      if (this.opts.nativeWebScreenshot) {
116        this.jwpProxyAvoid.push(['GET', new RegExp('^/session/[^/]+/screenshot')]);
117      }
118
119      if (this.opts.reboot) {
120        this.setAvdFromCapabilities(caps);
121        this.addWipeDataToAvdArgs();
122      }
123
124      // get device udid for this session
125      let {udid, emPort} = await helpers.getDeviceInfoFromCaps(this.opts);
126      this.opts.udid = udid;
127      this.opts.emPort = emPort;
128
129      // set up an instance of ADB
130      this.adb = await helpers.createADB(this.opts.javaVersion,
131                                         this.opts.udid,
132                                         this.opts.emPort,
133                                         this.opts.adbPort,
134                                         this.opts.suppressKillServer,
135                                         this.opts.remoteAdbHost,
136                                         this.opts.clearDeviceLogsOnStart);
137
138      if (this.helpers.isPackageOrBundle(this.opts.app)) {
139        // user provided package instead of app for 'app' capability, massage options
140        this.opts.appPackage = this.opts.app;
141        this.opts.app = null;
142      }
143
144      if (this.opts.app) {
145        // find and copy, or download and unzip an app url or path
146        this.opts.app = await this.helpers.configureApp(this.opts.app, APP_EXTENSION);
147        this.opts.appIsTemp = caps.app !== this.opts.app; // did we make a temporary copy?
148        await this.checkAppPresent();
149      } else if (this.appOnDevice) {
150        // the app isn't an actual app file but rather something we want to
151        // assume is on the device and just launch via the appPackage
152        log.info(`App file was not listed, instead we're going to run ` +
153                 `${this.opts.appPackage} directly on the device`);
154        await this.checkPackagePresent();
155      }
156
157      // Some cloud services using appium launch the avd themselves, so we ensure netspeed
158      // is set for emulators by calling adb.networkSpeed before running the app
159      if (util.hasValue(this.opts.networkSpeed)) {
160        if (!this.isEmulator()) {
161          log.warn("Sorry, networkSpeed capability is only available for emulators");
162        } else {
163          let networkSpeed = helpers.ensureNetworkSpeed(this.adb, this.opts.networkSpeed);
164          await this.adb.networkSpeed(networkSpeed);
165        }
166      }
167      // check if we have to enable/disable gps before running the application
168      if (util.hasValue(this.opts.gpsEnabled)) {
169        if (this.isEmulator()) {
170          log.info(`Trying to ${this.opts.gpsEnabled ? "enable" : "disable"} gps location provider`);
171          await this.adb.toggleGPSLocationProvider(this.opts.gpsEnabled);
172        } else {
173          log.warn('Sorry! gpsEnabled capability is only available for emulators');
174        }
175      }
176
177      await this.startAndroidSession(this.opts);
178      return [sessionId, this.caps];
179    } catch (e) {
180      // ignoring delete session exception if any and throw the real error
181      // that happened while creating the session.
182      try {
183        await this.deleteSession();
184      } catch (ign) {}
185      throw e;
186    }
187  }
188
189  isEmulator () {
190    return !!(this.opts.avd || /emulator/.test(this.opts.udid));
191  }
192
193  setAvdFromCapabilities (caps) {
194    if (this.opts.avd) {
195      log.info('avd name defined, ignoring device name and platform version');
196    } else {
197      if (!caps.deviceName) {
198        log.errorAndThrow('avd or deviceName should be specified when reboot option is enables');
199      }
200      if (!caps.platformVersion) {
201        log.errorAndThrow('avd or platformVersion should be specified when reboot option is enabled');
202      }
203      let avdDevice = caps.deviceName.replace(/[^a-zA-Z0-9_.]/g, "-");
204      this.opts.avd = `${avdDevice}__${caps.platformVersion}`;
205    }
206  }
207
208  addWipeDataToAvdArgs () {
209    if (!this.opts.avdArgs) {
210      this.opts.avdArgs = '-wipe-data';
211    } else  if (this.opts.avdArgs.toLowerCase().indexOf("-wipe-data") === -1) {
212      this.opts.avdArgs += ' -wipe-data';
213    }
214  }
215
216  get appOnDevice () {
217    return this.helpers.isPackageOrBundle(this.opts.app) || (!this.opts.app &&
218           this.helpers.isPackageOrBundle(this.opts.appPackage));
219  }
220
221  get isChromeSession () {
222    return helpers.isChromeBrowser(this.opts.browserName);
223  }
224
225  async onSettingsUpdate (key, value) {
226    if (key === "ignoreUnimportantViews") {
227      await this.setCompressedLayoutHierarchy(value);
228    }
229  }
230
231  async startAndroidSession () {
232    log.info(`Starting Android session`);
233    // set up the device to run on (real or emulator, etc)
234    this.defaultIME = await helpers.initDevice(this.adb, this.opts);
235
236    // set actual device name, udid, platform version, screen size, model and manufacturer details
237    this.caps.deviceName = this.adb.curDeviceId;
238    this.caps.deviceUDID = this.opts.udid;
239    this.caps.platformVersion = await this.adb.getPlatformVersion();
240    this.caps.deviceScreenSize = await this.adb.getScreenSize();
241    this.caps.deviceModel = await this.adb.getModel();
242    this.caps.deviceManufacturer = await this.adb.getManufacturer();
243
244    // If the user sets autoLaunch to false, they are responsible for initAUT() and startAUT()
245    if (this.opts.autoLaunch) {
246      // set up app under test
247      await this.initAUT();
248    }
249    // start UiAutomator
250    this.bootstrap = new helpers.bootstrap(this.adb, this.bootstrapPort, this.opts.websocket);
251    await this.bootstrap.start(this.opts.appPackage, this.opts.disableAndroidWatchers, this.opts.acceptSslCerts);
252    // handling unexpected shutdown
253    this.bootstrap.onUnexpectedShutdown.catch(async (err) => { // eslint-disable-line promise/prefer-await-to-callbacks
254      if (!this.bootstrap.ignoreUnexpectedShutdown) {
255        await this.startUnexpectedShutdown(err);
256      }
257    });
258
259    if (!this.opts.skipUnlock) {
260      // Let's try to unlock the device
261      await helpers.unlock(this, this.adb, this.caps);
262    }
263
264    // Set CompressedLayoutHierarchy on the device based on current settings object
265    // this has to happen _after_ bootstrap is initialized
266    if (this.opts.ignoreUnimportantViews) {
267      await this.settings.update({ignoreUnimportantViews: this.opts.ignoreUnimportantViews});
268    }
269
270    if (this.isChromeSession) {
271      // start a chromedriver session and proxy to it
272      await this.startChromeSession();
273      if (this.shouldDismissChromeWelcome()) {
274        // dismiss Chrome welcome dialog
275        await this.dismissChromeWelcome();
276      }
277    } else {
278      if (this.opts.autoLaunch) {
279        // start app
280        await this.startAUT();
281      }
282    }
283
284    if (util.hasValue(this.opts.orientation)) {
285      log.debug(`Setting initial orientation to '${this.opts.orientation}'`);
286      await this.setOrientation(this.opts.orientation);
287    }
288
289    await this.initAutoWebview();
290  }
291
292  shouldDismissChromeWelcome () {
293    return !_.isUndefined(this.opts.chromeOptions) &&
294      _.isArray(this.opts.chromeOptions.args) &&
295      this.opts.chromeOptions.args.indexOf('--no-first-run') !== -1;
296  }
297
298  async dismissChromeWelcome () {
299    log.info("Trying to dismiss Chrome welcome");
300    let activity = await this.getCurrentActivity();
301    if (activity !== "org.chromium.chrome.browser.firstrun.FirstRunActivity") {
302      log.info("Chrome welcome dialog never showed up! Continuing");
303      return;
304    }
305    let el = await this.findElOrEls('id', 'com.android.chrome:id/terms_accept', false);
306    await this.click(el.ELEMENT);
307    try {
308      let el = await this.findElOrEls('id', 'com.android.chrome:id/negative_button', false);
309      await this.click(el.ELEMENT);
310    } catch (e) {
311      // DO NOTHING, THIS DEVICE DIDNT LAUNCH THE SIGNIN DIALOG
312      // IT MUST BE A NON GMS DEVICE
313      log.warn(`This device didnt show Chrome SignIn dialog, ${e.message}`);
314    }
315  }
316
317  async initAutoWebview () {
318    if (this.opts.autoWebview) {
319      let viewName = this.defaultWebviewName();
320      let timeout = (this.opts.autoWebviewTimeout) || 2000;
321
322      log.info(`Setting auto webview to context '${viewName}' with timeout ${timeout}ms`);
323
324      // try every 500ms until timeout is over
325      await retryInterval(timeout / 500, 500, async () => {
326        await this.setContext(viewName);
327      });
328    }
329  }
330
331  async initAUT () {
332    // populate appPackage, appActivity, appWaitPackage, appWaitActivity,
333    // and the device being used
334    // in the opts and caps (so it gets back to the user on session creation)
335    let launchInfo = await helpers.getLaunchInfo(this.adb, this.opts);
336    Object.assign(this.opts, launchInfo);
337    Object.assign(this.caps, launchInfo);
338    // install app
339    if (!this.opts.app) {
340      if (this.opts.fullReset) {
341        log.errorAndThrow('Full reset requires an app capability, use fastReset if app is not provided');
342      }
343      log.debug('No app capability. Assuming it is already on the device');
344      if (this.opts.fastReset) {
345        await helpers.resetApp(this.adb, this.opts.app, this.opts.appPackage, this.opts.fastReset);
346      }
347      await this.grantPermissions();
348      return;
349    }
350    if (!this.opts.skipUninstall) {
351      await this.adb.uninstallApk(this.opts.appPackage);
352    }
353    await helpers.installApkRemotely(this.adb, this.opts);
354    await this.grantPermissions();
355    this.apkStrings[this.opts.language] = await helpers.pushStrings(
356        this.opts.language, this.adb, this.opts);
357
358    // This must run after installing the apk, otherwise it would cause the
359    // install to fail. And before running the app.
360    if (!_.isUndefined(this.opts.sharedPreferences)) {
361      await this.setSharedPreferences(this.opts);
362    }
363  }
364
365  async startChromeSession () {
366    log.info("Starting a chrome-based browser session");
367    let opts = _.cloneDeep(this.opts);
368    opts.chromeUseRunningApp = false;
369
370    const knownPackages = ["org.chromium.chrome.shell",
371                           "com.android.chrome",
372                           "com.chrome.beta",
373                           "org.chromium.chrome",
374                           "org.chromium.webview_shell"];
375
376    if (!_.includes(knownPackages, this.opts.appPackage)) {
377      opts.chromeAndroidActivity = this.opts.appActivity;
378    }
379    this.chromedriver = await setupNewChromedriver(opts, this.adb.curDeviceId,
380                                                   this.adb);
381    this.chromedriver.on(Chromedriver.EVENT_CHANGED, (msg) => {
382      if (msg.state === Chromedriver.STATE_STOPPED) {
383        this.onChromedriverStop(CHROMIUM_WIN);
384      }
385    });
386
387    // Now that we have a Chrome session, we ensure that the context is
388    // appropriately set and that this chromedriver is added to the list
389    // of session chromedrivers so we can switch back and forth
390    this.curContext = CHROMIUM_WIN;
391    this.sessionChromedrivers[CHROMIUM_WIN] = this.chromedriver;
392    this.proxyReqRes = this.chromedriver.proxyReq.bind(this.chromedriver);
393    this.jwpProxyActive = true;
394  }
395
396  async checkAppPresent () {
397    log.debug("Checking whether app is actually present");
398    if (!(await fs.exists(this.opts.app))) {
399      log.errorAndThrow(`Could not find app apk at ${this.opts.app}`);
400    }
401  }
402
403  async checkPackagePresent () {
404    log.debug("Checking whether package is present on the device");
405    if (!(await this.adb.shell(['pm', 'list', 'packages', this.opts.appPackage]))) {
406      log.errorAndThrow(`Could not find package ${this.opts.appPackage} on the device`);
407    }
408  }
409
410  async grantPermissions () {
411    if (this.opts.autoGrantPermissions) {
412      try {
413        await this.adb.grantAllPermissions(this.opts.appPackage, this.opts.app);
414      } catch (error) {
415        log.error(`Unable to grant permissions requested. Original error: ${error.message}`);
416      }
417    }
418  }
419  // Set CompressedLayoutHierarchy on the device
420  async setCompressedLayoutHierarchy (compress) {
421    await this.bootstrap.sendAction("compressedLayoutHierarchy", {compressLayout: compress});
422  }
423
424  async deleteSession () {
425    log.debug("Shutting down Android driver");
426    await super.deleteSession();
427    if (this.bootstrap) {
428      // certain cleanup we only care to do if the bootstrap was ever run
429      await this.stopChromedriverProxies();
430      if (this.opts.unicodeKeyboard && this.opts.resetKeyboard && this.defaultIME) {
431        log.debug(`Resetting IME to ${this.defaultIME}`);
432        await this.adb.setIME(this.defaultIME);
433      }
434      if (!this.isChromeSession) {
435        await this.adb.forceStop(this.opts.appPackage);
436      }
437      await this.adb.goToHome();
438      if (this.opts.fullReset && !this.opts.skipUninstall && !this.appOnDevice) {
439        await this.adb.uninstallApk(this.opts.appPackage);
440      }
441      await this.bootstrap.shutdown();
442      this.bootstrap = null;
443    } else {
444      log.debug("Called deleteSession but bootstrap wasn't active");
445    }
446    // some cleanup we want to do regardless, in case we are shutting down
447    // mid-startup
448    await this.adb.stopLogcat();
449    if (this.useUnlockHelperApp) {
450      await this.adb.forceStop('io.appium.unlock');
451    }
452    if (this.opts.reboot) {
453      let avdName = this.opts.avd.replace('@', '');
454      log.debug(`closing emulator '${avdName}'`);
455      await this.adb.killEmulator(avdName);
456    }
457    if (this.opts.clearSystemFiles) {
458      if (this.opts.appIsTemp) {
459        log.debug(`Temporary copy of app was made: deleting '${this.opts.app}'`);
460        try {
461          await fs.rimraf(this.opts.app);
462        } catch (err) {
463          log.warn(`Unable to delete temporary app: ${err.message}`);
464        }
465      } else {
466        log.debug('App was not copied, so not deleting');
467      }
468    } else {
469      log.debug('Not cleaning generated files. Add `clearSystemFiles` capability if wanted.');
470    }
471  }
472
473  async setSharedPreferences () {
474    let sharedPrefs = this.opts.sharedPreferences;
475    log.info("Trying to set shared preferences");
476    let name = sharedPrefs.name;
477    if (_.isUndefined(name)) {
478      log.warn(`Skipping setting Shared preferences, name is undefined: ${JSON.stringify(sharedPrefs)}`);
479      return false;
480    }
481    let remotePath = `/data/data/${this.opts.appPackage}/shared_prefs`;
482    let remoteFile = `${remotePath}/${name}.xml`;
483    let localPath = `/tmp/${name}.xml`;
484    let builder = this.getPrefsBuilder();
485    builder.build(sharedPrefs.prefs);
486    log.info(`Creating temporary shared preferences: ${localPath}`);
487    builder.toFile(localPath);
488    log.info(`Creating shared_prefs remote folder: ${remotePath}`);
489    await this.adb.shell(['mkdir', '-p', remotePath]);
490    log.info(`Pushing shared_prefs to ${remoteFile}`);
491    await this.adb.push(localPath, remoteFile);
492    try {
493      log.info(`Trying to remove shared preferences temporary file`);
494      if (await fs.exists(localPath)) {
495        await fs.unlink(localPath);
496      }
497    } catch (e) {
498      log.warn(`Error trying to remove temporary file ${localPath}`);
499    }
500    return true;
501  }
502
503  getPrefsBuilder () {
504    /* Add this method to create a new SharedPrefsBuilder instead of
505     * directly creating the object on setSharedPreferences for testing purposes
506    */
507    return new SharedPrefsBuilder();
508  }
509
510  validateDesiredCaps (caps) {
511    // check with the base class, and return if it fails
512    let res = super.validateDesiredCaps(caps);
513    if (!res) return res; // eslint-disable-line curly
514
515    // make sure that the capabilities have one of `app`, `appPackage` or `browser`
516    if ((!caps.browserName || !helpers.isChromeBrowser(caps.browserName)) &&
517      !caps.app && !caps.appPackage) {
518      let msg = 'The desired capabilities must include either an app, appPackage or browserName';
519      log.errorAndThrow(msg);
520    }
521    // warn if the capabilities have both `app` and `browser, although this
522    // is common with selenium grid
523    if (caps.browserName && caps.app) {
524      let msg = 'The desired capabilities should generally not include both an app and a browserName';
525      log.warn(msg);
526    }
527  }
528
529  proxyActive (sessionId) {
530    super.proxyActive(sessionId);
531
532    return this.jwpProxyActive;
533  }
534
535  getProxyAvoidList (sessionId) {
536    super.getProxyAvoidList(sessionId);
537
538    return this.jwpProxyAvoid;
539  }
540
541  canProxy (sessionId) {
542    super.canProxy(sessionId);
543
544    // this will change depending on ChromeDriver status
545    return _.isFunction(this.proxyReqRes);
546  }
547}
548
549export default AndroidDriver;
550
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)