How to use this.installOtherApps method in Appium Xcuitest Driver

Best JavaScript code snippet using appium-xcuitest-driver

Run Appium Xcuitest Driver automation tests on LambdaTest cloud grid

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

Sign up Free
_

driver.js

Source: driver.js Github

copy
1import { BaseDriver, DeviceSettings } from 'appium-base-driver';
2import { util, fs, mjpeg } from 'appium-support';
3import _ from 'lodash';
4import url from 'url';
5import { launch, openUrl } from 'node-simctl';
6import WebDriverAgent from './wda/webdriveragent';
7import log from './logger';
8import {
9  createSim, getExistingSim, runSimulatorReset, installToSimulator,
10  shutdownOtherSimulators, shutdownSimulator } from './simulator-management';
11import { simExists, getSimulator, installSSLCert, hasSSLCert } from 'appium-ios-simulator';
12import { retryInterval, retry } from 'asyncbox';
13import { settings as iosSettings, defaultServerCaps, appUtils } from 'appium-ios-driver';
14import { desiredCapConstraints, PLATFORM_NAME_IOS, PLATFORM_NAME_TVOS } from './desired-caps';
15import commands from './commands/index';
16import {
17  detectUdid, getAndCheckXcodeVersion, getAndCheckIosSdkVersion,
18  checkAppPresent, getDriverInfo,
19  clearSystemFiles, translateDeviceName, normalizeCommandTimeouts,
20  DEFAULT_TIMEOUT_KEY, markSystemFilesForCleanup,
21  printUser, removeAllSessionWebSocketHandlers, verifyApplicationPlatform, isTvOS,
22  normalizePlatformVersion, isLocalHost } from './utils';
23import {
24  getConnectedDevices, runRealDeviceReset, installToRealDevice,
25  getRealDeviceObj, getOSVersion } from './real-device-management';
26import B from 'bluebird';
27import AsyncLock from 'async-lock';
28import path from 'path';
29import IDB from 'appium-idb';
30import DEVICE_CONNECTIONS_FACTORY from './device-connections-factory';
31
32
33const SHUTDOWN_OTHER_FEAT_NAME = 'shutdown_other_sims';
34const SAFARI_BUNDLE_ID = 'com.apple.mobilesafari';
35const WDA_SIM_STARTUP_RETRIES = 2;
36const WDA_REAL_DEV_STARTUP_RETRIES = 1;
37const WDA_REAL_DEV_TUTORIAL_URL = 'https://github.com/appium/appium-xcuitest-driver/blob/master/docs/real-device-config.md';
38const WDA_STARTUP_RETRY_INTERVAL = 10000;
39const DEFAULT_SETTINGS = {
40  nativeWebTap: false,
41  useJSONSource: false,
42  shouldUseCompactResponses: true,
43  elementResponseAttributes: 'type,label',
44  // Read https://github.com/appium/WebDriverAgent/blob/master/WebDriverAgentLib/Utilities/FBConfiguration.m for following settings' values
45  mjpegServerScreenshotQuality: 25,
46  mjpegServerFramerate: 10,
47  screenshotQuality: 1,
48  mjpegScalingFactor: 100,
49  // set `reduceMotion` to `null` so that it will be verified but still set either true/false
50  reduceMotion: null,
51};
52// This lock assures, that each driver session does not
53// affect shared resources of the other parallel sessions
54const SHARED_RESOURCES_GUARD = new AsyncLock();
55
56/* eslint-disable no-useless-escape */
57const NO_PROXY_NATIVE_LIST = [
58  ['DELETE', /window/],
59  ['GET', /^\/session\/[^\/]+$/],
60  ['GET', /alert_text/],
61  ['GET', /alert\/[^\/]+/],
62  ['GET', /appium/],
63  ['GET', /attribute/],
64  ['GET', /context/],
65  ['GET', /location/],
66  ['GET', /log/],
67  ['GET', /screenshot/],
68  ['GET', /size/],
69  ['GET', /source/],
70  ['GET', /timeouts$/],
71  ['GET', /url/],
72  ['GET', /window/],
73  ['POST', /accept_alert/],
74  ['POST', /actions$/],
75  ['POST', /alert_text/],
76  ['POST', /alert\/[^\/]+/],
77  ['POST', /appium/],
78  ['POST', /appium\/device\/is_locked/],
79  ['POST', /appium\/device\/lock/],
80  ['POST', /appium\/device\/unlock/],
81  ['POST', /back/],
82  ['POST', /clear/],
83  ['POST', /context/],
84  ['POST', /dismiss_alert/],
85  ['POST', /element\/active/], // MJSONWP get active element should proxy
86  ['POST', /element$/],
87  ['POST', /elements$/],
88  ['POST', /execute/],
89  ['POST', /keys/],
90  ['POST', /log/],
91  ['POST', /moveto/],
92  ['POST', /receive_async_response/], // always, in case context switches while waiting
93  ['POST', /session\/[^\/]+\/location/], // geo location, but not element location
94  ['POST', /shake/],
95  ['POST', /timeouts/],
96  ['POST', /touch/],
97  ['POST', /url/],
98  ['POST', /value/],
99  ['POST', /window/],
100];
101const NO_PROXY_WEB_LIST = [
102  ['DELETE', /cookie/],
103  ['GET', /attribute/],
104  ['GET', /cookie/],
105  ['GET', /element/],
106  ['GET', /text/],
107  ['GET', /title/],
108  ['POST', /clear/],
109  ['POST', /click/],
110  ['POST', /cookie/],
111  ['POST', /element/],
112  ['POST', /forward/],
113  ['POST', /frame/],
114  ['POST', /keys/],
115  ['POST', /refresh/],
116].concat(NO_PROXY_NATIVE_LIST);
117/* eslint-enable no-useless-escape */
118
119const MEMOIZED_FUNCTIONS = [
120  'getStatusBarHeight',
121  'getDevicePixelRatio',
122  'getScreenInfo',
123  'getSafariIsIphone',
124  'getSafariIsIphoneX',
125];
126
127class XCUITestDriver extends BaseDriver {
128  constructor (opts = {}, shouldValidateCaps = true) {
129    super(opts, shouldValidateCaps);
130
131    this.desiredCapConstraints = desiredCapConstraints;
132
133    this.locatorStrategies = [
134      'xpath',
135      'id',
136      'name',
137      'class name',
138      '-ios predicate string',
139      '-ios class chain',
140      'accessibility id'
141    ];
142    this.webLocatorStrategies = [
143      'link text',
144      'css selector',
145      'tag name',
146      'link text',
147      'partial link text'
148    ];
149    this.resetIos();
150    this.settings = new DeviceSettings(DEFAULT_SETTINGS, this.onSettingsUpdate.bind(this));
151    this.logs = {};
152
153    // memoize functions here, so that they are done on a per-instance basis
154    for (const fn of MEMOIZED_FUNCTIONS) {
155      this[fn] = _.memoize(this[fn]);
156    }
157  }
158
159  async onSettingsUpdate (key, value) {
160    if (key !== 'nativeWebTap') {
161      return await this.proxyCommand('/appium/settings', 'POST', {
162        settings: {[key]: value}
163      });
164    }
165    this.opts.nativeWebTap = !!value;
166  }
167
168  resetIos () {
169    this.opts = this.opts || {};
170    this.wda = null;
171    this.opts.device = null;
172    this.jwpProxyActive = false;
173    this.proxyReqRes = null;
174    this.jwpProxyAvoid = [];
175    this.safari = false;
176    this.cachedWdaStatus = null;
177
178    // some things that commands imported from appium-ios-driver need
179    this.curWebFrames = [];
180    this.webElementIds = [];
181    this._currentUrl = null;
182    this.curContext = null;
183    this.xcodeVersion = {};
184    this.contexts = [];
185    this.implicitWaitMs = 0;
186    this.asynclibWaitMs = 0;
187    this.pageLoadMs = 6000;
188    this.landscapeWebCoordsOffset = 0;
189  }
190
191  get driverData () {
192    // TODO fill out resource info here
193    return {};
194  }
195
196  async getStatus () {
197    if (typeof this.driverInfo === 'undefined') {
198      this.driverInfo = await getDriverInfo();
199    }
200    let status = {build: {version: this.driverInfo.version}};
201    if (this.cachedWdaStatus) {
202      status.wda = this.cachedWdaStatus;
203    }
204    return status;
205  }
206
207  async createSession (...args) {
208    this.lifecycleData = {}; // this is used for keeping track of the state we start so when we delete the session we can put things back
209    try {
210      // TODO add validation on caps
211      let [sessionId, caps] = await super.createSession(...args);
212      this.opts.sessionId = sessionId;
213
214      await this.start();
215
216      // merge server capabilities + desired capabilities
217      caps = Object.assign({}, defaultServerCaps, caps);
218      // update the udid with what is actually used
219      caps.udid = this.opts.udid;
220      // ensure we track nativeWebTap capability as a setting as well
221      if (_.has(this.opts, 'nativeWebTap')) {
222        await this.updateSettings({nativeWebTap: this.opts.nativeWebTap});
223      }
224      // ensure we track useJSONSource capability as a setting as well
225      if (_.has(this.opts, 'useJSONSource')) {
226        await this.updateSettings({useJSONSource: this.opts.useJSONSource});
227      }
228
229      let wdaSettings = {
230        elementResponseAttributes: DEFAULT_SETTINGS.elementResponseAttributes,
231        shouldUseCompactResponses: DEFAULT_SETTINGS.shouldUseCompactResponses,
232      };
233      if (_.has(this.opts, 'elementResponseAttributes')) {
234        wdaSettings.elementResponseAttributes = this.opts.elementResponseAttributes;
235      }
236      if (_.has(this.opts, 'shouldUseCompactResponses')) {
237        wdaSettings.shouldUseCompactResponses = this.opts.shouldUseCompactResponses;
238      }
239      if (_.has(this.opts, 'mjpegServerScreenshotQuality')) {
240        wdaSettings.mjpegServerScreenshotQuality = this.opts.mjpegServerScreenshotQuality;
241      }
242      if (_.has(this.opts, 'mjpegServerFramerate')) {
243        wdaSettings.mjpegServerFramerate = this.opts.mjpegServerFramerate;
244      }
245      if (_.has(this.opts, 'screenshotQuality')) {
246        log.info(`Setting the quality of phone screenshot: '${this.opts.screenshotQuality}'`);
247        wdaSettings.screenshotQuality = this.opts.screenshotQuality;
248      }
249      // ensure WDA gets our defaults instead of whatever its own might be
250      await this.updateSettings(wdaSettings);
251
252      // turn on mjpeg stream reading if requested
253      if (this.opts.mjpegScreenshotUrl) {
254        log.info(`Starting MJPEG stream reading URL: '${this.opts.mjpegScreenshotUrl}'`);
255        this.mjpegStream = new mjpeg.MJpegStream(this.opts.mjpegScreenshotUrl);
256        await this.mjpegStream.start();
257      }
258      return [sessionId, caps];
259    } catch (e) {
260      log.error(e);
261      await this.deleteSession();
262      throw e;
263    }
264  }
265
266  async start () {
267    this.opts.noReset = !!this.opts.noReset;
268    this.opts.fullReset = !!this.opts.fullReset;
269
270    await printUser();
271
272    this.opts.iosSdkVersion = null; // For WDA and xcodebuild
273    const {device, udid, realDevice} = await this.determineDevice();
274    log.info(`Determining device to run tests on: udid: '${udid}', real device: ${realDevice}`);
275    this.opts.device = device;
276    this.opts.udid = udid;
277    this.opts.realDevice = realDevice;
278
279    const normalizedVersion = normalizePlatformVersion(this.opts.platformVersion);
280    if (this.opts.platformVersion !== normalizedVersion) {
281      log.info(`Normalized platformVersion capability value '${this.opts.platformVersion}' to '${normalizedVersion}'`);
282      this.opts.platformVersion = normalizedVersion;
283    }
284    if (util.compareVersions(this.opts.platformVersion, '<', '9.3')) {
285      throw new Error(`Platform version must be 9.3 or above. '${this.opts.platformVersion}' is not supported.`);
286    }
287
288    if (_.isEmpty(this.xcodeVersion) && (!this.opts.webDriverAgentUrl || !this.opts.realDevice)) {
289      // no `webDriverAgentUrl`, or on a simulator, so we need an Xcode version
290      this.xcodeVersion = await getAndCheckXcodeVersion();
291    }
292    this.logEvent('xcodeDetailsRetrieved');
293
294    if (this.opts.enableAsyncExecuteFromHttps && !this.isRealDevice()) {
295      // shutdown the simulator so that the ssl cert is recognized
296      await shutdownSimulator(this.opts.device);
297      await this.startHttpsAsyncServer();
298    }
299
300    // at this point if there is no platformVersion, get it from the device
301    if (!this.opts.platformVersion) {
302      if (this.opts.device && _.isFunction(this.opts.device.getPlatformVersion)) {
303        this.opts.platformVersion = await this.opts.device.getPlatformVersion();
304        log.info(`No platformVersion specified. Using device version: '${this.opts.platformVersion}'`);
305      } else {
306        // TODO: this is when it is a real device. when we have a real object wire it in
307      }
308    }
309
310    if ((this.opts.browserName || '').toLowerCase() === 'safari') {
311      log.info('Safari test requested');
312      this.safari = true;
313      this.opts.app = undefined;
314      this.opts.processArguments = this.opts.processArguments || {};
315      this.opts.bundleId = SAFARI_BUNDLE_ID;
316      this._currentUrl = this.opts.safariInitialUrl || (
317        this.isRealDevice()
318          ? 'http://appium.io'
319          : `http://${this.opts.address}:${this.opts.port}/welcome`
320      );
321      if (util.compareVersions(this.opts.platformVersion, '<', '12.2')) {
322        // this option does not work on 12.2 and above
323        this.opts.processArguments.args = ['-u', this._currentUrl];
324      }
325    } else {
326      await this.configureApp();
327    }
328    this.logEvent('appConfigured');
329
330    // fail very early if the app doesn't actually exist
331    // or if bundle id doesn't point to an installed app
332    if (this.opts.app) {
333      await checkAppPresent(this.opts.app);
334    }
335
336    if (!this.opts.bundleId) {
337      this.opts.bundleId = await appUtils.extractBundleId(this.opts.app);
338    }
339
340    await this.runReset();
341
342    const memoizedLogInfo = _.memoize(function logInfo () {
343      log.info("'skipLogCapture' is set. Skipping starting logs such as crash, system, safari console and safari network.");
344    });
345    const startLogCapture = async () => {
346      if (this.opts.skipLogCapture) {
347        memoizedLogInfo();
348        return false;
349      }
350
351      const result = await this.startLogCapture();
352      if (result) {
353        this.logEvent('logCaptureStarted');
354      }
355      return result;
356    };
357    const isLogCaptureStarted = await startLogCapture();
358
359    log.info(`Setting up ${this.isRealDevice() ? 'real device' : 'simulator'}`);
360
361    if (this.isSimulator()) {
362      if (this.opts.shutdownOtherSimulators) {
363        this.ensureFeatureEnabled(SHUTDOWN_OTHER_FEAT_NAME);
364        await shutdownOtherSimulators(this.opts.device);
365      }
366
367      // this should be done before the simulator is started
368      // if it is already running, this cap won't work, which is documented
369      if (this.isSafari() && this.opts.safariGlobalPreferences) {
370        if (await this.opts.device.updateSafariGlobalSettings(this.opts.safariGlobalPreferences)) {
371          log.debug(`Safari global preferences updated`);
372        }
373      }
374
375      this.localConfig = await iosSettings.setLocaleAndPreferences(this.opts.device, this.opts, this.isSafari(), async (sim) => {
376        await shutdownSimulator(sim);
377
378        // we don't know if there needs to be changes a priori, so change first.
379        // sometimes the shutdown process changes the settings, so reset them,
380        // knowing that the sim is already shut
381        await iosSettings.setLocaleAndPreferences(sim, this.opts, this.isSafari());
382      });
383
384      await this.startSim();
385
386      if (this.opts.customSSLCert) {
387        if (await hasSSLCert(this.opts.customSSLCert, this.opts.udid)) {
388          log.info(`SSL cert '${_.truncate(this.opts.customSSLCert, {length: 20})}' already installed`);
389        } else {
390          log.info(`Installing ssl cert '${_.truncate(this.opts.customSSLCert, {length: 20})}'`);
391          await shutdownSimulator(this.opts.device);
392          await installSSLCert(this.opts.customSSLCert, this.opts.udid);
393          log.info(`Restarting Simulator so that SSL certificate installation takes effect`);
394          await this.startSim();
395          this.logEvent('customCertInstalled');
396        }
397      }
398
399      try {
400        const idb = new IDB({udid});
401        await idb.connect();
402        this.opts.device.idb = idb;
403      } catch (e) {
404        log.info(`idb will not be used for Simulator interaction. Original error: ${e.message}`);
405      }
406
407      this.logEvent('simStarted');
408      if (!isLogCaptureStarted) {
409        // Retry log capture if Simulator was not running before
410        await startLogCapture();
411      }
412    }
413
414    if (this.opts.app) {
415      await this.installAUT();
416      this.logEvent('appInstalled');
417    }
418
419    // if we only have bundle identifier and no app, fail if it is not already installed
420    if (!this.opts.app && this.opts.bundleId && !this.safari) {
421      if (!await this.opts.device.isAppInstalled(this.opts.bundleId)) {
422        log.errorAndThrow(`App with bundle identifier '${this.opts.bundleId}' unknown`);
423      }
424    }
425
426    if (this.opts.permissions) {
427      if (this.isSimulator()) {
428        log.debug('Setting the requested permissions before WDA is started');
429        for (const [bundleId, permissionsMapping] of _.toPairs(JSON.parse(this.opts.permissions))) {
430          await this.opts.device.setPermissions(bundleId, permissionsMapping);
431        }
432      } else {
433        log.warn('Setting permissions is only supported on Simulator. ' +
434          'The "permissions" capability will be ignored.');
435      }
436    }
437
438    await this.startWda(this.opts.sessionId, realDevice);
439
440    await this.setReduceMotion(this.opts.reduceMotion);
441
442    await this.setInitialOrientation(this.opts.orientation);
443    this.logEvent('orientationSet');
444
445    // real devices will be handled later, after the web context has been initialized
446    if (this.isSafari() && !this.isRealDevice() && util.compareVersions(this.opts.platformVersion, '>=', '12.2')) {
447      // on 12.2 the page is not opened in WDA
448      await openUrl(this.opts.device.udid, this._currentUrl);
449    }
450
451    if (this.isSafari() || this.opts.autoWebview) {
452      log.debug('Waiting for initial webview');
453      await this.navToInitialWebview();
454      this.logEvent('initialWebviewNavigated');
455    }
456
457    if (this.isSafari() && this.isRealDevice() && util.compareVersions(this.opts.platformVersion, '>=', '12.2')) {
458      // on 12.2 the page is not opened in WDA
459      await this.setUrl(this._currentUrl);
460    }
461
462    if (!this.isRealDevice()) {
463      if (this.opts.calendarAccessAuthorized) {
464        await this.opts.device.enableCalendarAccess(this.opts.bundleId);
465      } else if (this.opts.calendarAccessAuthorized === false) {
466        await this.opts.device.disableCalendarAccess(this.opts.bundleId);
467      }
468    }
469  }
470
471  /**
472   * Start WebDriverAgentRunner
473   * @param {string} sessionId - The id of the target session to launch WDA with.
474   * @param {boolean} realDevice - Equals to true if the test target device is a real device.
475   */
476  async startWda (sessionId, realDevice) {
477    this.wda = new WebDriverAgent(this.xcodeVersion, this.opts);
478
479    // Don't cleanup the processes if webDriverAgentUrl is set
480    if (!util.hasValue(this.wda.webDriverAgentUrl)) {
481      await this.wda.cleanupObsoleteProcesses();
482    }
483
484    const usePortForwarding = this.isRealDevice()
485      && !this.wda.webDriverAgentUrl
486      && isLocalHost(this.wda.wdaBaseUrl);
487    await DEVICE_CONNECTIONS_FACTORY.requestConnection(this.opts.udid, this.wda.url.port, {
488      devicePort: this.wda.wdaRemotePort,
489      usePortForwarding,
490    });
491
492    // Let multiple WDA binaries with different derived data folders be built in parallel
493    // Concurrent WDA builds from the same source will cause xcodebuild synchronization errors
494    let synchronizationKey = XCUITestDriver.name;
495    if (this.opts.useXctestrunFile || !(await this.wda.isSourceFresh())) {
496      // First-time compilation is an expensive operation, which is done faster if executed
497      // sequentially. Xcodebuild spreads the load caused by the clang compiler to all available CPU cores
498      const derivedDataPath = await this.wda.retrieveDerivedDataPath();
499      if (derivedDataPath) {
500        synchronizationKey = path.normalize(derivedDataPath);
501      }
502    }
503    log.debug(`Starting WebDriverAgent initialization with the synchronization key '${synchronizationKey}'`);
504    if (SHARED_RESOURCES_GUARD.isBusy() && !this.opts.derivedDataPath && !this.opts.bootstrapPath) {
505      log.debug(`Consider setting a unique 'derivedDataPath' capability value for each parallel driver instance ` +
506        `to avoid conflicts and speed up the building process`);
507    }
508    return await SHARED_RESOURCES_GUARD.acquire(synchronizationKey, async () => {
509      if (this.opts.useNewWDA) {
510        log.debug(`Capability 'useNewWDA' set to true, so uninstalling WDA before proceeding`);
511        await this.wda.quitAndUninstall();
512        this.logEvent('wdaUninstalled');
513      } else if (!util.hasValue(this.wda.webDriverAgentUrl)) {
514        await this.wda.setupCaching();
515      }
516
517      // local helper for the two places we need to uninstall wda and re-start it
518      const quitAndUninstall = async (msg) => {
519        log.debug(msg);
520        if (this.opts.webDriverAgentUrl) {
521          log.debug('Not quitting/uninstalling WebDriverAgent since webDriverAgentUrl capability is provided');
522          throw new Error(msg);
523        }
524        log.warn('Quitting and uninstalling WebDriverAgent');
525        await this.wda.quitAndUninstall();
526
527        throw new Error(msg);
528      };
529
530      const startupRetries = this.opts.wdaStartupRetries || (this.isRealDevice() ? WDA_REAL_DEV_STARTUP_RETRIES : WDA_SIM_STARTUP_RETRIES);
531      const startupRetryInterval = this.opts.wdaStartupRetryInterval || WDA_STARTUP_RETRY_INTERVAL;
532      log.debug(`Trying to start WebDriverAgent ${startupRetries} times with ${startupRetryInterval}ms interval`);
533      if (!util.hasValue(this.opts.wdaStartupRetries) && !util.hasValue(this.opts.wdaStartupRetryInterval)) {
534        log.debug(`These values can be customized by changing wdaStartupRetries/wdaStartupRetryInterval capabilities`);
535      }
536      let retryCount = 0;
537      await retryInterval(startupRetries, startupRetryInterval, async () => {
538        this.logEvent('wdaStartAttempted');
539        if (retryCount > 0) {
540          log.info(`Retrying WDA startup (${retryCount + 1} of ${startupRetries})`);
541        }
542        try {
543          // on xcode 10 installd will often try to access the app from its staging
544          // directory before fully moving it there, and fail. Retrying once
545          // immediately helps
546          const retries = this.xcodeVersion.major >= 10 ? 2 : 1;
547          this.cachedWdaStatus = await retry(retries, this.wda.launch.bind(this.wda), sessionId, realDevice);
548        } catch (err) {
549          this.logEvent('wdaStartFailed');
550          retryCount++;
551          let errorMsg = `Unable to launch WebDriverAgent because of xcodebuild failure: ${err.message}`;
552          if (this.isRealDevice()) {
553            errorMsg += `. Make sure you follow the tutorial at ${WDA_REAL_DEV_TUTORIAL_URL}. ` +
554                        `Try to remove the WebDriverAgentRunner application from the device if it is installed ` +
555                        `and reboot the device.`;
556          }
557          await quitAndUninstall(errorMsg);
558        }
559
560        this.proxyReqRes = this.wda.proxyReqRes.bind(this.wda);
561        this.jwpProxyActive = true;
562
563        let originalStacktrace = null;
564        try {
565          await retryInterval(15, 1000, async () => {
566            this.logEvent('wdaSessionAttempted');
567            log.debug('Sending createSession command to WDA');
568            try {
569              this.cachedWdaStatus = this.cachedWdaStatus || await this.proxyCommand('/status', 'GET');
570              await this.startWdaSession(this.opts.bundleId, this.opts.processArguments);
571            } catch (err) {
572              originalStacktrace = err.stack;
573              log.debug(`Failed to create WDA session (${err.message}). Retrying...`);
574              throw err;
575            }
576          });
577          this.logEvent('wdaSessionStarted');
578        } catch (err) {
579          if (originalStacktrace) {
580            log.debug(originalStacktrace);
581          }
582          let errorMsg = `Unable to start WebDriverAgent session because of xcodebuild failure: ${err.message}`;
583          if (this.isRealDevice()) {
584            errorMsg += ` Make sure you follow the tutorial at ${WDA_REAL_DEV_TUTORIAL_URL}. ` +
585                        `Try to remove the WebDriverAgentRunner application from the device if it is installed ` +
586                        `and reboot the device.`;
587          }
588          await quitAndUninstall(errorMsg);
589        }
590
591        if (this.opts.clearSystemFiles && !this.opts.webDriverAgentUrl) {
592          await markSystemFilesForCleanup(this.wda);
593        }
594
595        // we expect certain socket errors until this point, but now
596        // mark things as fully working
597        this.wda.fullyStarted = true;
598        this.logEvent('wdaStarted');
599      });
600    });
601  }
602
603  async runReset (opts = null) {
604    this.logEvent('resetStarted');
605    if (this.isRealDevice()) {
606      await runRealDeviceReset(this.opts.device, opts || this.opts);
607    } else {
608      await runSimulatorReset(this.opts.device, opts || this.opts);
609    }
610    this.logEvent('resetComplete');
611  }
612
613  async deleteSession () {
614    await removeAllSessionWebSocketHandlers(this.server, this.sessionId);
615
616    if (this.isSimulator() && (this.opts.device || {}).idb) {
617      await this.opts.device.idb.disconnect();
618      this.opts.device.idb = null;
619    }
620
621    await this.stop();
622
623    if (this.opts.clearSystemFiles && this.isAppTemporary) {
624      await fs.rimraf(this.opts.app);
625    }
626
627    if (this.wda && !this.opts.webDriverAgentUrl) {
628      if (this.opts.clearSystemFiles) {
629        let synchronizationKey = XCUITestDriver.name;
630        const derivedDataPath = await this.wda.retrieveDerivedDataPath();
631        if (derivedDataPath) {
632          synchronizationKey = path.normalize(derivedDataPath);
633        }
634        await SHARED_RESOURCES_GUARD.acquire(synchronizationKey, async () => {
635          await clearSystemFiles(this.wda);
636        });
637      } else {
638        log.debug('Not clearing log files. Use `clearSystemFiles` capability to turn on.');
639      }
640    }
641
642    if (this.isWebContext()) {
643      log.debug('In a web session. Removing remote debugger');
644      await this.stopRemote();
645    }
646
647    if (this.opts.resetOnSessionStartOnly === false) {
648      await this.runReset(Object.assign({}, this.opts, {
649        enforceSimulatorShutdown: true,
650      }));
651    }
652
653    if (this.isSimulator() && !this.opts.noReset && !!this.opts.device) {
654      if (this.lifecycleData.createSim) {
655        log.debug(`Deleting simulator created for this run (udid: '${this.opts.udid}')`);
656        await shutdownSimulator(this.opts.device);
657        await this.opts.device.delete();
658      }
659    }
660
661    if (!_.isEmpty(this.logs)) {
662      await this.logs.syslog.stopCapture();
663      this.logs = {};
664    }
665
666    if (this.opts.enableAsyncExecuteFromHttps && !this.isRealDevice()) {
667      await this.stopHttpsAsyncServer();
668    }
669
670    if (this.mjpegStream) {
671      log.info('Closing MJPEG stream');
672      this.mjpegStream.stop();
673    }
674
675    this.resetIos();
676
677    await super.deleteSession();
678  }
679
680  async stop () {
681    this.jwpProxyActive = false;
682    this.proxyReqRes = null;
683
684
685    if (this.wda && this.wda.fullyStarted) {
686      if (this.wda.jwproxy) {
687        try {
688          await this.proxyCommand(`/session/${this.sessionId}`, 'DELETE');
689        } catch (err) {
690          // an error here should not short-circuit the rest of clean up
691          log.debug(`Unable to DELETE session on WDA: '${err.message}'. Continuing shutdown.`);
692        }
693      }
694      if (!this.wda.webDriverAgentUrl && this.opts.useNewWDA) {
695        await this.wda.quit();
696      }
697    }
698
699    DEVICE_CONNECTIONS_FACTORY.releaseConnection(this.opts.udid);
700  }
701
702  async executeCommand (cmd, ...args) {
703    log.debug(`Executing command '${cmd}'`);
704
705    if (cmd === 'receiveAsyncResponse') {
706      return await this.receiveAsyncResponse(...args);
707    }
708    // TODO: once this fix gets into base driver remove from here
709    if (cmd === 'getStatus') {
710      return await this.getStatus();
711    }
712    return await super.executeCommand(cmd, ...args);
713  }
714
715  async configureApp () {
716    function appIsPackageOrBundle (app) {
717      return (/^([a-zA-Z0-9\-_]+\.[a-zA-Z0-9\-_]+)+$/).test(app);
718    }
719
720    // the app name is a bundleId assign it to the bundleId property
721    if (!this.opts.bundleId && appIsPackageOrBundle(this.opts.app)) {
722      this.opts.bundleId = this.opts.app;
723      this.opts.app = '';
724    }
725    // we have a bundle ID, but no app, or app is also a bundle
726    if ((this.opts.bundleId && appIsPackageOrBundle(this.opts.bundleId)) &&
727        (this.opts.app === '' || appIsPackageOrBundle(this.opts.app))) {
728      log.debug('App is an iOS bundle, will attempt to run as pre-existing');
729      return;
730    }
731
732    // check for supported build-in apps
733    if (this.opts.app && this.opts.app.toLowerCase() === 'settings') {
734      this.opts.bundleId = 'com.apple.Preferences';
735      this.opts.app = null;
736      return;
737    } else if (this.opts.app && this.opts.app.toLowerCase() === 'calendar') {
738      this.opts.bundleId = 'com.apple.mobilecal';
739      this.opts.app = null;
740      return;
741    }
742
743    const originalAppPath = this.opts.app;
744    try {
745      // download if necessary
746      this.opts.app = await this.helpers.configureApp(this.opts.app, '.app');
747    } catch (err) {
748      log.error(err);
749      throw new Error(`Bad app: ${this.opts.app}. App paths need to be absolute or an URL to a compressed app file${err && err.message ? `: ${err.message}` : ''}`);
750    }
751    this.isAppTemporary = this.opts.app && await fs.exists(this.opts.app)
752      && !await util.isSameDestination(originalAppPath, this.opts.app);
753  }
754
755  async determineDevice () {
756    // in the one case where we create a sim, we will set this state
757    this.lifecycleData.createSim = false;
758
759    // if we get generic names, translate them
760    this.opts.deviceName = translateDeviceName(this.opts.platformVersion, this.opts.deviceName);
761
762    const setupVersionCaps = async () => {
763      this.opts.iosSdkVersion = await getAndCheckIosSdkVersion();
764      log.info(`iOS SDK Version set to '${this.opts.iosSdkVersion}'`);
765      if (!this.opts.platformVersion && this.opts.iosSdkVersion) {
766        log.info(`No platformVersion specified. Using the latest version Xcode supports: '${this.opts.iosSdkVersion}'. ` +
767          `This may cause problems if a simulator does not exist for this platform version.`);
768        this.opts.platformVersion = normalizePlatformVersion(this.opts.iosSdkVersion);
769      }
770    };
771
772    if (this.opts.udid) {
773      if (this.opts.udid.toLowerCase() === 'auto') {
774        try {
775          this.opts.udid = await detectUdid();
776        } catch (err) {
777          // Trying to find matching UDID for Simulator
778          log.warn(`Cannot detect any connected real devices. Falling back to Simulator. Original error: ${err.message}`);
779          const device = await getExistingSim(this.opts);
780          if (!device) {
781            // No matching Simulator is found. Throw an error
782            log.errorAndThrow(`Cannot detect udid for ${this.opts.deviceName} Simulator running iOS ${this.opts.platformVersion}`);
783          }
784
785          // Matching Simulator exists and is found. Use it
786          this.opts.udid = device.udid;
787          const devicePlatform = normalizePlatformVersion(await device.getPlatformVersion());
788          if (this.opts.platformVersion !== devicePlatform) {
789            this.opts.platformVersion = devicePlatform;
790            log.info(`Set platformVersion to '${devicePlatform}' to match the device with given UDID`);
791          }
792          await setupVersionCaps();
793          return {device, realDevice: false, udid: device.udid};
794        }
795      } else {
796        // make sure it is a connected device. If not, the udid passed in is invalid
797        const devices = await getConnectedDevices();
798        log.debug(`Available devices: ${devices.join(', ')}`);
799        if (!devices.includes(this.opts.udid)) {
800          // check for a particular simulator
801          if (await simExists(this.opts.udid)) {
802            const device = await getSimulator(this.opts.udid);
803            return {device, realDevice: false, udid: this.opts.udid};
804          }
805
806          throw new Error(`Unknown device or simulator UDID: '${this.opts.udid}'`);
807        }
808      }
809
810      const device = await getRealDeviceObj(this.opts.udid);
811      if (_.isEmpty(this.opts.platformVersion)) {
812        log.info('Getting the platformVersion from the phone since it was not specified in the capabilities');
813        try {
814          const osVersion = await getOSVersion(this.opts.udid);
815          this.opts.platformVersion = util.coerceVersion(osVersion);
816        } catch (e) {
817          log.warn(`Cannot determine real device platform version. Original error: ${e.message}`);
818        }
819      }
820      return {device, realDevice: true, udid: this.opts.udid};
821    }
822
823    // Now we know for sure the device will be a Simulator
824    await setupVersionCaps();
825    if (this.opts.enforceFreshSimulatorCreation) {
826      log.debug(`New simulator is requested. If this is not wanted, set 'enforceFreshSimulatorCreation' capability to false`);
827    } else {
828      // figure out the correct simulator to use, given the desired capabilities
829      const device = await getExistingSim(this.opts);
830
831      // check for an existing simulator
832      if (device) {
833        return {device, realDevice: false, udid: device.udid};
834      }
835
836      log.info('Simulator udid not provided');
837    }
838
839    // no device of this type exists, or they request new sim, so create one
840    log.info('Using desired caps to create a new simulator');
841    const device = await this.createSim();
842    return {device, realDevice: false, udid: device.udid};
843  }
844
845  async startSim () {
846    const runOpts = {
847      scaleFactor: this.opts.scaleFactor,
848      connectHardwareKeyboard: !!this.opts.connectHardwareKeyboard,
849      isHeadless: !!this.opts.isHeadless,
850      devicePreferences: {},
851    };
852
853    // add the window center, if it is specified
854    if (this.opts.SimulatorWindowCenter) {
855      runOpts.devicePreferences.SimulatorWindowCenter = this.opts.SimulatorWindowCenter;
856    }
857
858    // This is to workaround XCTest bug about changing Simulator
859    // orientation is not synchronized to the actual window orientation
860    const orientation = _.isString(this.opts.orientation) && this.opts.orientation.toUpperCase();
861    switch (orientation) {
862      case 'LANDSCAPE':
863        runOpts.devicePreferences.SimulatorWindowOrientation = 'LandscapeLeft';
864        runOpts.devicePreferences.SimulatorWindowRotationAngle = 90;
865        break;
866      case 'PORTRAIT':
867        runOpts.devicePreferences.SimulatorWindowOrientation = 'Portrait';
868        runOpts.devicePreferences.SimulatorWindowRotationAngle = 0;
869        break;
870    }
871
872    await this.opts.device.run(runOpts);
873  }
874
875  async createSim () {
876    this.lifecycleData.createSim = true;
877
878    // Get platform name from const since it must be case sensitive to create a new simulator
879    const platformName = isTvOS(this.opts.platformName) ? PLATFORM_NAME_TVOS : PLATFORM_NAME_IOS;
880
881    // create sim for caps
882    let sim = await createSim(this.opts, platformName);
883    log.info(`Created simulator with udid '${sim.udid}'.`);
884
885    return sim;
886  }
887
888  async launchApp () {
889    const APP_LAUNCH_TIMEOUT = 20 * 1000;
890
891    this.logEvent('appLaunchAttempted');
892    await launch(this.opts.device.udid, this.opts.bundleId);
893
894    let checkStatus = async () => {
895      let response = await this.proxyCommand('/status', 'GET');
896      let currentApp = response.currentApp.bundleID;
897      if (currentApp !== this.opts.bundleId) {
898        throw new Error(`${this.opts.bundleId} not in foreground. ${currentApp} is in foreground`);
899      }
900    };
901
902    log.info(`Waiting for '${this.opts.bundleId}' to be in foreground`);
903    let retries = parseInt(APP_LAUNCH_TIMEOUT / 200, 10);
904    await retryInterval(retries, 200, checkStatus);
905    log.info(`${this.opts.bundleId} is in foreground`);
906    this.logEvent('appLaunched');
907  }
908
909  async startWdaSession (bundleId, processArguments) {
910    let args = processArguments ? (processArguments.args || []) : [];
911    if (!_.isArray(args)) {
912      throw new Error(`processArguments.args capability is expected to be an array. ` +
913                      `${JSON.stringify(args)} is given instead`);
914    }
915    let env = processArguments ? (processArguments.env || {}) : {};
916    if (!_.isPlainObject(env)) {
917      throw new Error(`processArguments.env capability is expected to be a dictionary. ` +
918                      `${JSON.stringify(env)} is given instead`);
919    }
920
921    let shouldWaitForQuiescence = util.hasValue(this.opts.waitForQuiescence) ? this.opts.waitForQuiescence : true;
922    let maxTypingFrequency = util.hasValue(this.opts.maxTypingFrequency) ? this.opts.maxTypingFrequency : 60;
923    let shouldUseSingletonTestManager = util.hasValue(this.opts.shouldUseSingletonTestManager) ? this.opts.shouldUseSingletonTestManager : true;
924    let shouldUseTestManagerForVisibilityDetection = false;
925    let eventloopIdleDelaySec = this.opts.wdaEventloopIdleDelay || 0;
926    if (util.hasValue(this.opts.simpleIsVisibleCheck)) {
927      shouldUseTestManagerForVisibilityDetection = this.opts.simpleIsVisibleCheck;
928    }
929    if (util.compareVersions(this.opts.platformVersion, '==', '9.3')) {
930      log.info(`Forcing shouldUseSingletonTestManager capability value to true, because of known XCTest issues under 9.3 platform version`);
931      shouldUseTestManagerForVisibilityDetection = true;
932    }
933    if (util.hasValue(this.opts.language)) {
934      args.push('-AppleLanguages', `(${this.opts.language})`);
935      args.push('-NSLanguages', `(${this.opts.language})`);
936    }
937
938    if (util.hasValue(this.opts.locale)) {
939      args.push('-AppleLocale', this.opts.locale);
940    }
941
942    const wdaCaps = {
943      bundleId: this.opts.autoLaunch === false ? undefined : bundleId,
944      arguments: args,
945      environment: env,
946      eventloopIdleDelaySec,
947      shouldWaitForQuiescence,
948      shouldUseTestManagerForVisibilityDetection,
949      maxTypingFrequency,
950      shouldUseSingletonTestManager,
951    };
952    if (util.hasValue(this.opts.shouldUseCompactResponses)) {
953      wdaCaps.shouldUseCompactResponses = this.opts.shouldUseCompactResponses;
954    }
955    if (util.hasValue(this.opts.elementResponseFields)) {
956      wdaCaps.elementResponseFields = this.opts.elementResponseFields;
957    }
958    if (this.opts.autoAcceptAlerts) {
959      wdaCaps.defaultAlertAction = 'accept';
960    } else if (this.opts.autoDismissAlerts) {
961      wdaCaps.defaultAlertAction = 'dismiss';
962    }
963
964    await this.proxyCommand('/session', 'POST', {
965      capabilities: {
966        firstMatch: [wdaCaps],
967        alwaysMatch: {},
968      }
969    });
970  }
971
972  // Override Proxy methods from BaseDriver
973  proxyActive () {
974    return this.jwpProxyActive;
975  }
976
977  getProxyAvoidList () {
978    if (this.isWebview()) {
979      return NO_PROXY_WEB_LIST;
980    }
981    return NO_PROXY_NATIVE_LIST;
982  }
983
984  canProxy () {
985    return true;
986  }
987
988  isSafari () {
989    return !!this.safari;
990  }
991
992  isRealDevice () {
993    return this.opts.realDevice;
994  }
995
996  isSimulator () {
997    return !this.opts.realDevice;
998  }
999
1000  isWebview () {
1001    return this.isSafari() || this.isWebContext();
1002  }
1003
1004  validateLocatorStrategy (strategy) {
1005    super.validateLocatorStrategy(strategy, this.isWebContext());
1006  }
1007
1008  validateDesiredCaps (caps) {
1009    if (!super.validateDesiredCaps(caps)) {
1010      return false;
1011    }
1012
1013    // make sure that the capabilities have one of `app` or `bundleId`
1014    if ((caps.browserName || '').toLowerCase() !== 'safari' && !caps.app && !caps.bundleId) {
1015      let msg = 'The desired capabilities must include either an app or a bundleId for iOS';
1016      log.errorAndThrow(msg);
1017    }
1018
1019    if (!util.coerceVersion(caps.platformVersion, false)) {
1020      log.warn(`'platformVersion' capability ('${caps.platformVersion}') is not a valid version number. ` +
1021        `Consider fixing it or be ready to experience an inconsistent driver behavior.`);
1022    }
1023
1024    let verifyProcessArgument = (processArguments) => {
1025      const {args, env} = processArguments;
1026      if (!_.isNil(args) && !_.isArray(args)) {
1027        log.errorAndThrow('processArguments.args must be an array of strings');
1028      }
1029      if (!_.isNil(env) && !_.isPlainObject(env)) {
1030        log.errorAndThrow('processArguments.env must be an object <key,value> pair {a:b, c:d}');
1031      }
1032    };
1033
1034    // `processArguments` should be JSON string or an object with arguments and/ environment details
1035    if (caps.processArguments) {
1036      if (_.isString(caps.processArguments)) {
1037        try {
1038          // try to parse the string as JSON
1039          caps.processArguments = JSON.parse(caps.processArguments);
1040          verifyProcessArgument(caps.processArguments);
1041        } catch (err) {
1042          log.errorAndThrow(`processArguments must be a json format or an object with format {args : [], env : {a:b, c:d}}. ` +
1043            `Both environment and argument can be null. Error: ${err}`);
1044        }
1045      } else if (_.isPlainObject(caps.processArguments)) {
1046        verifyProcessArgument(caps.processArguments);
1047      } else {
1048        log.errorAndThrow(`'processArguments must be an object, or a string JSON object with format {args : [], env : {a:b, c:d}}. ` +
1049          `Both environment and argument can be null.`);
1050      }
1051    }
1052
1053    // there is no point in having `keychainPath` without `keychainPassword`
1054    if ((caps.keychainPath && !caps.keychainPassword) || (!caps.keychainPath && caps.keychainPassword)) {
1055      log.errorAndThrow(`If 'keychainPath' is set, 'keychainPassword' must also be set (and vice versa).`);
1056    }
1057
1058    // `resetOnSessionStartOnly` should be set to true by default
1059    this.opts.resetOnSessionStartOnly = !util.hasValue(this.opts.resetOnSessionStartOnly) || this.opts.resetOnSessionStartOnly;
1060    this.opts.useNewWDA = util.hasValue(this.opts.useNewWDA) ? this.opts.useNewWDA : false;
1061
1062    if (caps.commandTimeouts) {
1063      caps.commandTimeouts = normalizeCommandTimeouts(caps.commandTimeouts);
1064    }
1065
1066    if (_.isString(caps.webDriverAgentUrl)) {
1067      const {protocol, host} = url.parse(caps.webDriverAgentUrl);
1068      if (_.isEmpty(protocol) || _.isEmpty(host)) {
1069        log.errorAndThrow(`'webDriverAgentUrl' capability is expected to contain a valid WebDriverAgent server URL. ` +
1070                          `'${caps.webDriverAgentUrl}' is given instead`);
1071      }
1072    }
1073
1074    if (caps.browserName) {
1075      if (caps.bundleId) {
1076        log.errorAndThrow(`'browserName' cannot be set together with 'bundleId' capability`);
1077      }
1078      // warn if the capabilities have both `app` and `browser, although this
1079      // is common with selenium grid
1080      if (caps.app) {
1081        log.warn(`The capabilities should generally not include both an 'app' and a 'browserName'`);
1082      }
1083    }
1084
1085    if (caps.permissions) {
1086      try {
1087        for (const [bundleId, perms] of _.toPairs(JSON.parse(caps.permissions))) {
1088          if (!_.isString(bundleId)) {
1089            throw new Error(`'${JSON.stringify(bundleId)}' must be a string`);
1090          }
1091          if (!_.isPlainObject(perms)) {
1092            throw new Error(`'${JSON.stringify(perms)}' must be a JSON object`);
1093          }
1094        }
1095      } catch (e) {
1096        log.errorAndThrow(`'${caps.permissions}' is expected to be a valid object with format ` +
1097          `{"<bundleId1>": {"<serviceName1>": "<serviceStatus1>", ...}, ...}. Original error: ${e.message}`);
1098      }
1099    }
1100
1101    if (caps.platformVersion && !util.coerceVersion(caps.platformVersion, false)) {
1102      log.errorAndThrow(`'platformVersion' must be a valid version number. ` +
1103        `'${caps.platformVersion}' is given instead.`);
1104    }
1105
1106    // finally, return true since the superclass check passed, as did this
1107    return true;
1108  }
1109
1110  async installAUT () {
1111    if (this.isSafari()) {
1112      return;
1113    }
1114
1115    try {
1116      await verifyApplicationPlatform(this.opts.app, this.isSimulator(), isTvOS(this.opts.platformName));
1117    } catch (err) {
1118      // TODO: Let it throw after we confirm the architecture verification algorithm is stable
1119      log.warn(`*********************************`);
1120      log.warn(`${this.isSimulator() ? 'Simulator' : 'Real device'} architecture appears to be unsupported ` +
1121               `by the '${this.opts.app}' application. ` +
1122               `Make sure the correct deployment target has been selected for its compilation in Xcode.`);
1123      log.warn('Don\'t be surprised if the application fails to launch.');
1124      log.warn(`*********************************`);
1125    }
1126
1127    if (this.isRealDevice()) {
1128      await installToRealDevice(this.opts.device, this.opts.app, this.opts.bundleId, this.opts.noReset);
1129    } else {
1130      await installToSimulator(this.opts.device, this.opts.app, this.opts.bundleId, this.opts.noReset);
1131    }
1132    if (this.opts.otherApps) {
1133      await this.installOtherApps(this.opts.otherApps);
1134    }
1135
1136    if (util.hasValue(this.opts.iosInstallPause)) {
1137      // https://github.com/appium/appium/issues/6889
1138      let pause = parseInt(this.opts.iosInstallPause, 10);
1139      log.debug(`iosInstallPause set. Pausing ${pause} ms before continuing`);
1140      await B.delay(pause);
1141    }
1142  }
1143
1144  async installOtherApps (otherApps) {
1145    if (this.isRealDevice()) {
1146      log.warn('Capability otherApps is only supported for Simulators');
1147      return;
1148    }
1149    try {
1150      otherApps = this.helpers.parseCapsArray(otherApps);
1151    } catch (e) {
1152      log.errorAndThrow(`Could not parse "otherApps" capability: ${e.message}`);
1153    }
1154    for (const otherApp of otherApps) {
1155      await installToSimulator(this.opts.device, otherApp, undefined, this.opts.noReset);
1156    }
1157  }
1158
1159  /**
1160   * Set reduceMotion as 'isEnabled' only when the capabilities has 'reduceMotion'
1161   * The call is ignored for real devices.
1162   * @param {?boolean} isEnabled Wether enable reduceMotion
1163   */
1164  async setReduceMotion (isEnabled) {
1165    if (this.isRealDevice() || !_.isBoolean(isEnabled)) {
1166      return;
1167    }
1168
1169    log.info(`Setting reduceMotion to ${isEnabled}`);
1170    await this.updateSettings({reduceMotion: isEnabled});
1171  }
1172
1173  async setInitialOrientation (orientation) {
1174    if (!_.isString(orientation)) {
1175      log.info('Skipping setting of the initial display orientation. ' +
1176        'Set the "orientation" capability to either "LANDSCAPE" or "PORTRAIT", if this is an undesired behavior.');
1177      return;
1178    }
1179    orientation = orientation.toUpperCase();
1180    if (!_.includes(['LANDSCAPE', 'PORTRAIT'], orientation)) {
1181      log.debug(`Unable to set initial orientation to '${orientation}'`);
1182      return;
1183    }
1184    log.debug(`Setting initial orientation to '${orientation}'`);
1185    try {
1186      await this.proxyCommand('/orientation', 'POST', {orientation});
1187      this.opts.curOrientation = orientation;
1188    } catch (err) {
1189      log.warn(`Setting initial orientation failed with: ${err.message}`);
1190    }
1191  }
1192
1193  _getCommandTimeout (cmdName) {
1194    if (this.opts.commandTimeouts) {
1195      if (cmdName && _.has(this.opts.commandTimeouts, cmdName)) {
1196        return this.opts.commandTimeouts[cmdName];
1197      }
1198      return this.opts.commandTimeouts[DEFAULT_TIMEOUT_KEY];
1199    }
1200  }
1201
1202  /**
1203   * Get session capabilities merged with what WDA reports
1204   * This is a library command but needs to call 'super' so can't be on
1205   * a helper object
1206   */
1207  async getSession () {
1208    // call super to get event timings, etc...
1209    const driverSession = await super.getSession();
1210    if (!this.wdaCaps) {
1211      this.wdaCaps = await this.proxyCommand('/', 'GET');
1212    }
1213    if (!this.deviceCaps) {
1214      const {statusBarSize, scale} = await this.getScreenInfo();
1215      this.deviceCaps = {
1216        pixelRatio: scale,
1217        statBarHeight: statusBarSize.height,
1218        viewportRect: await this.getViewportRect(),
1219      };
1220    }
1221    log.info('Merging WDA caps over Appium caps for session detail response');
1222    return Object.assign({udid: this.opts.udid}, driverSession,
1223      this.wdaCaps.capabilities, this.deviceCaps);
1224  }
1225
1226  async reset () {
1227    if (this.opts.noReset) {
1228      // This is to make sure reset happens even if noReset is set to true
1229      let opts = _.cloneDeep(this.opts);
1230      opts.noReset = false;
1231      opts.fullReset = false;
1232      const shutdownHandler = this.resetOnUnexpectedShutdown;
1233      this.resetOnUnexpectedShutdown = () => {};
1234      try {
1235        await this.runReset(opts);
1236      } finally {
1237        this.resetOnUnexpectedShutdown = shutdownHandler;
1238      }
1239    }
1240    await super.reset();
1241  }
1242}
1243
1244Object.assign(XCUITestDriver.prototype, commands);
1245
1246export default XCUITestDriver;
1247export { XCUITestDriver };
1248
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 Xcuitest 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)