How to use verifyApplicationPlatform 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
_

app-utils.js

Source: app-utils.js Github

copy
1import _ from 'lodash';
2import path from 'path';
3import { plist, fs, util } from '@appium/support';
4import log from './logger.js';
5
6const STRINGSDICT_RESOURCE = '.stringsdict';
7const STRINGS_RESOURCE = '.strings';
8
9
10async function extractPlistEntry (app, entryName) {
11  const plistPath = path.resolve(app, 'Info.plist');
12  try {
13    return (await plist.parsePlistFile(plistPath))[entryName];
14  } catch (err) {
15    throw new Error(`Could not extract Info.plist from '${path.basename(app)}': ${err.message}`);
16  }
17}
18
19async function extractBundleId (app) {
20  const bundleId = await extractPlistEntry(app, 'CFBundleIdentifier');
21  log.debug(`Getting bundle ID from app '${app}': '${bundleId}'`);
22  return bundleId;
23}
24
25/**
26 * @typedef {Object} PlatformOpts
27 *
28 * @property {boolean} isSimulator - Whether the destination platform is a Simulator
29 * @property {boolean} isTvOS - Whether the destination platform is a Simulator
30 */
31
32/**
33 * Verify whether the given application is compatible to the
34 * platform where it is going to be installed and tested.
35 *
36 * @param {string} app - The actual path to the application bundle
37 * @param {PlatformOpts} expectedPlatform
38 * @throws {Error} If bundle architecture does not match the expected device architecture.
39 */
40async function verifyApplicationPlatform (app, expectedPlatform) {
41  log.debug('Verifying application platform');
42
43  let supportedPlatforms;
44  try {
45    supportedPlatforms = await extractPlistEntry(app, 'CFBundleSupportedPlatforms');
46  } catch (err) {
47    log.debug(err.message);
48    return;
49  }
50  log.debug(`CFBundleSupportedPlatforms: ${JSON.stringify(supportedPlatforms)}`);
51  if (!_.isArray(supportedPlatforms)) {
52    log.debug(`CFBundleSupportedPlatforms key does not exist in '${path.basename(app)}'`);
53    return;
54  }
55
56  const {
57    isSimulator,
58    isTvOS,
59  } = expectedPlatform;
60  const prefix = isTvOS ? 'AppleTV' : 'iPhone';
61  const suffix = isSimulator ? 'Simulator' : 'OS';
62  const dstPlatform = `${prefix}${suffix}`;
63  if (!supportedPlatforms.includes(dstPlatform)) {
64    throw new Error(`${isSimulator ? 'Simulator' : 'Real device'} architecture is unsupported by the '${app}' application. ` +
65      `Make sure the correct deployment target has been selected for its compilation in Xcode.`);
66  }
67}
68
69async function readResource (resourcePath) {
70  const data = await plist.parsePlistFile(resourcePath);
71  const result = {};
72  for (const [key, value] of _.toPairs(data)) {
73    result[key] = _.isString(value) ? value : JSON.stringify(value);
74  }
75  return result;
76}
77
78async function parseLocalizableStrings (opts) {
79  const {
80    app,
81    language = 'en',
82    localizableStringsDir,
83    stringFile,
84    strictMode,
85  } = opts;
86
87  if (!app) {
88    const message = `Strings extraction is not supported if 'app' capability is not set`;
89    if (strictMode) {
90      throw new Error(message);
91    }
92    log.info(message);
93    return {};
94  }
95
96  let lprojRoot;
97  for (const subfolder of [`${language}.lproj`, localizableStringsDir, '']) {
98    lprojRoot = path.resolve(app, subfolder);
99    if (await fs.exists(lprojRoot)) {
100      break;
101    }
102    const message = `No '${lprojRoot}' resources folder has been found`;
103    if (strictMode) {
104      throw new Error(message);
105    }
106    log.debug(message);
107  }
108  log.info(`Will extract resource strings from '${lprojRoot}'`);
109
110  const resourcePaths = [];
111  if (stringFile) {
112    const dstPath = path.resolve(lprojRoot, stringFile);
113    if (await fs.exists(dstPath)) {
114      resourcePaths.push(dstPath);
115    } else {
116      const message = `No '${dstPath}' resource file has been found for '${app}'`;
117      if (strictMode) {
118        throw new Error(message);
119      }
120      log.info(message);
121      log.info(`Getting all the available strings from '${lprojRoot}'`);
122    }
123  }
124
125  if (_.isEmpty(resourcePaths) && await fs.exists(lprojRoot)) {
126    const resourceFiles = (await fs.readdir(lprojRoot))
127      .filter((name) => _.some([STRINGS_RESOURCE, STRINGSDICT_RESOURCE], (x) => name.endsWith(x)))
128      .map((name) => path.resolve(lprojRoot, name));
129    resourcePaths.push(...resourceFiles);
130  }
131  log.info(`Got ${resourcePaths.length} resource file(s) in '${lprojRoot}'`);
132
133  if (_.isEmpty(resourcePaths)) {
134    return {};
135  }
136
137  const resultStrings = {};
138  const toAbsolutePath = function (p) {
139    return path.isAbsolute(p) ? p : path.resolve(process.cwd(), p);
140  };
141  for (const resourcePath of resourcePaths) {
142    if (!util.isSubPath(toAbsolutePath(resourcePath), toAbsolutePath(app))) {
143      // security precaution
144      throw new Error(`'${resourcePath}' is expected to be located under '${app}'`);
145    }
146    try {
147      const data = await readResource(resourcePath);
148      log.debug(`Parsed ${_.keys(data).length} string(s) from '${resourcePath}'`);
149      _.merge(resultStrings, data);
150    } catch (e) {
151      log.warn(`Cannot parse '${resourcePath}' resource. Original error: ${e.message}`);
152    }
153  }
154
155  log.info(`Got ${_.keys(resultStrings).length} string(s) from '${lprojRoot}'`);
156  return resultStrings;
157}
158
159export { extractBundleId, verifyApplicationPlatform, parseLocalizableStrings };
160
Full Screen

utils.js

Source: utils.js Github

copy
1import B from 'bluebird';
2import { utilities } from 'appium-ios-device';
3import { fs, util, net, plist } from 'appium-support';
4import path from 'path';
5import { utils as iosUtils } from 'appium-ios-driver';
6import { exec } from 'teen_process';
7import xcode from 'appium-xcode';
8import _ from 'lodash';
9import log from './logger';
10import iosGenericSimulators from './ios-generic-simulators';
11import url from 'url';
12import os from 'os';
13import semver from 'semver';
14
15
16const DEFAULT_TIMEOUT_KEY = 'default';
17const XCTEST_LOG_FILES_PATTERNS = [
18  /^Session-WebDriverAgentRunner.*\.log$/i,
19  /^StandardOutputAndStandardError\.txt$/i,
20];
21const XCTEST_LOGS_CACHE_FOLDER_PREFIX = 'com.apple.dt.XCTest';
22
23
24async function detectUdid () {
25  log.debug('Auto-detecting real device udid...');
26  const udids = await utilities.getConnectedDevices();
27  if (_.isEmpty(udids)) {
28    throw new Error('No device is connected to the host');
29  }
30  const udid = _.last(udids);
31  if (udids.length > 1) {
32    log.warn(`Multiple devices found: ${udids.join(', ')}`);
33    log.warn(`Choosing '${udid}'. If this is wrong, manually set with 'udid' desired capability`);
34  }
35  log.debug(`Detected real device udid: '${udid}'`);
36  return udid;
37}
38
39async function getAndCheckXcodeVersion () {
40  let version;
41  try {
42    version = await xcode.getVersion(true);
43  } catch (err) {
44    log.debug(err);
45    log.errorAndThrow(`Could not determine Xcode version: ${err.message}`);
46  }
47
48  // we do not support Xcodes < 7.3,
49  if (version.versionFloat < 7.3) {
50    log.errorAndThrow(`Xcode version '${version.versionString}'. Support for ` +
51                      `Xcode ${version.versionString} is not supported. ` +
52                      `Please upgrade to version 7.3 or higher`);
53  }
54  return version;
55}
56
57async function getAndCheckIosSdkVersion () {
58  try {
59    return await xcode.getMaxIOSSDK();
60  } catch (err) {
61    log.errorAndThrow(`Could not determine iOS SDK version: ${err.message}`);
62  }
63}
64
65/**
66 * Get the generic simulator for a given IOS version and device type (iPhone, iPad)
67 *
68 * @param {string|number} platformVersion IOS version. e.g.) 13.0
69 * @param {string} deviceName Type of IOS device. Can be iPhone, iPad (possibly more in the future)
70 *
71 * @returns {string} Generic iPhone or iPad simulator (if applicable)
72 */
73function getGenericSimulatorForIosVersion (platformVersion, deviceName) {
74  let genericSimulators = iosGenericSimulators[deviceName];
75
76  if (genericSimulators) {
77    genericSimulators = genericSimulators.sort(([simOne], [simTwo]) => util.compareVersions(simOne, '<', simTwo) ? -1 : 1);
78
79    // Find the highest iOS version in the list that is below the provided version
80    let genericIosSimulator;
81    for (const [platformVersionFromList, iosSimulator] of genericSimulators) {
82      if (util.compareVersions(platformVersionFromList, '>', platformVersion)) {
83        break;
84      }
85      genericIosSimulator = iosSimulator;
86    }
87    return genericIosSimulator;
88  }
89}
90
91function translateDeviceName (platformVersion, deviceName = '') {
92  const deviceNameTranslated = getGenericSimulatorForIosVersion(platformVersion, deviceName.toLowerCase().trim());
93  if (deviceNameTranslated) {
94    log.debug(`Changing deviceName from '${deviceName}' to '${deviceNameTranslated}'`);
95    return deviceNameTranslated;
96  }
97  return deviceName;
98}
99
100// This map contains derived data logs folders as keys
101// and values are the count of times the particular
102// folder has been scheduled for removal
103const derivedDataCleanupMarkers = new Map();
104
105async function markSystemFilesForCleanup (wda) {
106  if (!wda || !await wda.retrieveDerivedDataPath()) {
107    log.warn('No WebDriverAgent derived data available, so unable to mark system files for cleanup');
108    return;
109  }
110
111  const logsRoot = path.resolve(await wda.retrieveDerivedDataPath(), 'Logs');
112  let markersCount = 0;
113  if (derivedDataCleanupMarkers.has(logsRoot)) {
114    markersCount = derivedDataCleanupMarkers.get(logsRoot);
115  }
116  derivedDataCleanupMarkers.set(logsRoot, ++markersCount);
117}
118
119async function clearSystemFiles (wda) {
120  // only want to clear the system files for the particular WDA xcode run
121  if (!wda || !await wda.retrieveDerivedDataPath()) {
122    log.warn('No WebDriverAgent derived data available, so unable to clear system files');
123    return;
124  }
125
126  const logsRoot = path.resolve(await wda.retrieveDerivedDataPath(), 'Logs');
127  if (derivedDataCleanupMarkers.has(logsRoot)) {
128    let markersCount = derivedDataCleanupMarkers.get(logsRoot);
129    derivedDataCleanupMarkers.set(logsRoot, --markersCount);
130    if (markersCount > 0) {
131      log.info(`Not cleaning '${logsRoot}' folder, because the other session does not expect it to be cleaned`);
132      return;
133    }
134  }
135  derivedDataCleanupMarkers.set(logsRoot, 0);
136
137  // Cleaning up big temporary files created by XCTest: https://github.com/appium/appium/issues/9410
138  const globPattern = `${os.tmpdir()}/${XCTEST_LOGS_CACHE_FOLDER_PREFIX}*/`;
139  const dstFolders = await fs.glob(globPattern);
140  if (_.isEmpty(dstFolders)) {
141    log.debug(`Did not find the temporary XCTest logs root at '${globPattern}'`);
142  } else {
143    // perform the cleanup asynchronously
144    for (const dstFolder of dstFolders) {
145      let scheduledFilesCount = 0;
146      B.resolve(fs.walkDir(dstFolder, true, (itemPath, isDir) => {
147        if (isDir) {
148          return;
149        }
150        const fileName = path.basename(itemPath);
151        if (!XCTEST_LOG_FILES_PATTERNS.some((p) => p.test(fileName))) {
152          return;
153        }
154
155        // delete the file asynchronously
156        fs.unlink(itemPath).catch((e) => {
157          log.info(e.message);
158        });
159        scheduledFilesCount++;
160      })).finally(() => {
161        if (scheduledFilesCount > 0) {
162          log.info(`Scheduled ${scheduledFilesCount} temporary XCTest log ` +
163            `${util.pluralize('file', scheduledFilesCount)} for cleanup in '${dstFolder}'`);
164        }
165      }).catch((e) => {
166        log.info(e.message);
167      });
168    }
169    log.debug(`Started background XCTest logs cleanup in '${dstFolders}'`);
170  }
171
172  if (await fs.exists(logsRoot)) {
173    log.info(`Cleaning test logs in '${logsRoot}' folder`);
174    await iosUtils.clearLogs([logsRoot]);
175    return;
176  }
177  log.info(`There is no ${logsRoot} folder, so not cleaning files`);
178}
179
180async function checkAppPresent (app) {
181  log.debug(`Checking whether app '${app}' is actually present on file system`);
182  if (!(await fs.exists(app))) {
183    log.errorAndThrow(`Could not find app at '${app}'`);
184  }
185  log.debug('App is present');
186}
187
188async function getDriverInfo () {
189  const stat = await fs.stat(path.resolve(__dirname, '..'));
190  const built = stat.mtime.getTime();
191
192  // get the package.json and the version from it
193  const pkg = require(__filename.includes('build/lib/utils') ? '../../package.json' : '../package.json');
194  const version = pkg.version;
195
196  return {
197    built,
198    version,
199  };
200}
201
202function normalizeCommandTimeouts (value) {
203  // The value is normalized already
204  if (typeof value !== 'string') {
205    return value;
206  }
207
208  let result = {};
209  // Use as default timeout for all commands if a single integer value is provided
210  if (!isNaN(value)) {
211    result[DEFAULT_TIMEOUT_KEY] = _.toInteger(value);
212    return result;
213  }
214
215  // JSON object has been provided. Let's parse it
216  try {
217    result = JSON.parse(value);
218    if (!_.isPlainObject(result)) {
219      throw new Error();
220    }
221  } catch (err) {
222    log.errorAndThrow(`"commandTimeouts" capability should be a valid JSON object. "${value}" was given instead`);
223  }
224  for (let [cmd, timeout] of _.toPairs(result)) {
225    if (!_.isInteger(timeout) || timeout <= 0) {
226      log.errorAndThrow(`The timeout for "${cmd}" should be a valid natural number of milliseconds. "${timeout}" was given instead`);
227    }
228  }
229  return result;
230}
231
232async function printUser () {
233  try {
234    let {stdout} = await exec('whoami');
235    log.debug(`Current user: '${stdout.trim()}'`);
236  } catch (err) {
237    log.debug(`Unable to get username running server: ${err.message}`);
238  }
239}
240
241/**
242 * Get the IDs of processes listening on the particular system port.
243 * It is also possible to apply additional filtering based on the
244 * process command line.
245 *
246 * @param {string|number} port - The port number.
247 * @param {?Function} filteringFunc - Optional lambda function, which
248 *                                    receives command line string of the particular process
249 *                                    listening on given port, and is expected to return
250 *                                    either true or false to include/exclude the corresponding PID
251 *                                    from the resulting array.
252 * @returns {Array<string>} - the list of matched process ids.
253 */
254async function getPIDsListeningOnPort (port, filteringFunc = null) {
255  const result = [];
256  try {
257    // This only works since Mac OS X El Capitan
258    const {stdout} = await exec('lsof', ['-ti', `tcp:${port}`]);
259    result.push(...(stdout.trim().split(/\n+/)));
260  } catch (e) {
261    return result;
262  }
263
264  if (!_.isFunction(filteringFunc)) {
265    return result;
266  }
267  return await B.filter(result, async (x) => {
268    const {stdout} = await exec('ps', ['-p', x, '-o', 'command']);
269    return await filteringFunc(stdout);
270  });
271}
272
273/**
274 * @typedef {Object} UploadOptions
275 *
276 * @property {?string} user - The name of the user for the remote authentication. Only works if `remotePath` is provided.
277 * @property {?string} pass - The password for the remote authentication. Only works if `remotePath` is provided.
278 * @property {?string} method - The http multipart upload method name. The 'PUT' one is used by default.
279 *                              Only works if `remotePath` is provided.
280 * @property {?Object} headers - Additional headers mapping for multipart http(s) uploads
281 * @property {?string} fileFieldName [file] - The name of the form field, where the file content BLOB should be stored for
282 *                                            http(s) uploads
283 * @property {?Object|Array<Pair>} formFields - Additional form fields for multipart http(s) uploads
284 */
285
286
287/**
288 * Encodes the given local file to base64 and returns the resulting string
289 * or uploads it to a remote server using http/https or ftp protocols
290 * if `remotePath` is set
291 *
292 * @param {string} localPath - The path to an existing local file
293 * @param {?string} remotePath - The path to the remote location, where
294 *                               this file should be uploaded
295 * @param {?UploadOptions} uploadOptions - Set of upload options
296 * @returns {string} Either an empty string if the upload was successful or
297 * base64-encoded file representation if `remotePath` is falsy
298 */
299async function encodeBase64OrUpload (localPath, remotePath = null, uploadOptions = {}) {
300  if (!await fs.exists(localPath)) {
301    log.errorAndThrow(`The file at '${localPath}' does not exist or is not accessible`);
302  }
303
304  if (_.isEmpty(remotePath)) {
305    const {size} = await fs.stat(localPath);
306    log.debug(`The size of the file is ${util.toReadableSizeString(size)}`);
307    return (await util.toInMemoryBase64(localPath)).toString();
308  }
309
310  const {user, pass, method, headers, fileFieldName, formFields} = uploadOptions;
311  const options = {
312    method: method || 'PUT',
313    headers,
314    fileFieldName,
315    formFields,
316  };
317  if (user && pass) {
318    options.auth = {user, pass};
319  }
320  await net.uploadFile(localPath, remotePath, options);
321  return '';
322}
323
324/**
325 * Stops and removes all web socket handlers that are listening
326 * in scope of the currect session.
327 *
328 * @param {Object} server - The instance of NodeJs HTTP server,
329 * which hosts Appium
330 * @param {string} sessionId - The id of the current session
331 */
332async function removeAllSessionWebSocketHandlers (server, sessionId) {
333  if (!server || !_.isFunction(server.getWebSocketHandlers)) {
334    return;
335  }
336
337  const activeHandlers = await server.getWebSocketHandlers(sessionId);
338  for (const pathname of _.keys(activeHandlers)) {
339    await server.removeWebSocketHandler(pathname);
340  }
341}
342
343/**
344 * @typedef {Object} PlatformOpts
345 *
346 * @property {boolean} isSimulator - Whether the destination platform is a Simulator
347 * @property {boolean} isTvOS - Whether the destination platform is a Simulator
348 */
349
350/**
351 * Verify whether the given application is compatible to the
352 * platform where it is going to be installed and tested.
353 *
354 * @param {string} app - The actual path to the application bundle
355 * @param {PlatformOpts} expectedPlatform
356 * @throws {Error} If bundle architecture does not match the expected device architecture.
357 */
358async function verifyApplicationPlatform (app, expectedPlatform) {
359  log.debug('Verifying application platform');
360
361  const infoPlist = path.resolve(app, 'Info.plist');
362  if (!await fs.exists(infoPlist)) {
363    log.debug(`'${infoPlist}' does not exist`);
364    return;
365  }
366
367  const {CFBundleSupportedPlatforms} = await plist.parsePlistFile(infoPlist);
368  log.debug(`CFBundleSupportedPlatforms: ${JSON.stringify(CFBundleSupportedPlatforms)}`);
369  if (!_.isArray(CFBundleSupportedPlatforms)) {
370    log.debug(`CFBundleSupportedPlatforms key does not exist in '${infoPlist}'`);
371    return;
372  }
373
374  const {
375    isSimulator,
376    isTvOS,
377  } = expectedPlatform;
378  const prefix = isTvOS ? 'AppleTV' : 'iPhone';
379  const suffix = isSimulator ? 'Simulator' : 'OS';
380  const dstPlatform = `${prefix}${suffix}`;
381  if (!CFBundleSupportedPlatforms.includes(dstPlatform)) {
382    throw new Error(`${isSimulator ? 'Simulator' : 'Real device'} architecture is unsupported by the '${app}' application. ` +
383      `Make sure the correct deployment target has been selected for its compilation in Xcode.`);
384  }
385}
386
387/**
388 * Returns true if the urlString is localhost
389 * @param {?string} urlString
390 * @returns {boolean} Return true if the urlString is localhost
391 */
392function isLocalHost (urlString) {
393  try {
394    const {hostname} = url.parse(urlString);
395    return ['localhost', '127.0.0.1', '::1', '::ffff:127.0.0.1'].includes(hostname);
396  } catch (ign) {
397    log.warn(`'${urlString}' cannot be parsed as a valid URL`);
398  }
399  return false;
400}
401
402/**
403 * Normalizes platformVersion to a valid iOS version string
404 *
405 * @param {string} originalVersion - Loose version number, that can be parsed by semver
406 * @return {string} iOS version number in <major>.<minor> format
407 * @throws {Error} if the version number cannot be parsed
408 */
409function normalizePlatformVersion (originalVersion) {
410  const normalizedVersion = semver.coerce(originalVersion);
411  if (!normalizedVersion) {
412    throw new Error(`The platform version '${originalVersion}' should be a valid version number`);
413  }
414  return `${normalizedVersion.major}.${normalizedVersion.minor}`;
415}
416
417
418export { detectUdid, getAndCheckXcodeVersion, getAndCheckIosSdkVersion, getGenericSimulatorForIosVersion,
419  checkAppPresent, getDriverInfo,
420  clearSystemFiles, translateDeviceName, normalizeCommandTimeouts,
421  DEFAULT_TIMEOUT_KEY, markSystemFilesForCleanup, printUser,
422  getPIDsListeningOnPort, encodeBase64OrUpload, removeAllSessionWebSocketHandlers,
423  verifyApplicationPlatform, isLocalHost, normalizePlatformVersion };
424
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)