Best JavaScript code snippet using appium-xcuitest-driver
driver.js
Source:driver.js
...467 }468 this.logEvent('resetComplete');469 }470 async deleteSession () {471 await removeAllSessionWebSocketHandlers(this.server, this.sessionId);472 await SHARED_RESOURCES_GUARD.acquire(XCUITestDriver.name, async () => {473 await this.stop();474 // reset the permissions on the derived data folder, if necessary475 if (this.opts.preventWDAAttachments) {476 await adjustWDAAttachmentsPermissions(this.wda, '755');477 }478 if (this.opts.clearSystemFiles) {479 if (this.isAppTemporary) {480 await fs.rimraf(this.opts.app);481 }482 await clearSystemFiles(this.wda, !!this.opts.showXcodeLog);483 } else {484 log.debug('Not clearing log files. Use `clearSystemFiles` capability to turn on.');485 }...
android-helpers.js
Source:android-helpers.js
1import _ from 'lodash';2import path from 'path';3import { exec } from 'teen_process';4import { retry, retryInterval, waitForCondition } from 'asyncbox';5import logger from './logger';6import { fs, util } from 'appium-support';7import { path as settingsApkPath } from 'io.appium.settings';8import Bootstrap from './bootstrap';9import B from 'bluebird';10import ADB from 'appium-adb';11import {12 default as unlocker, PIN_UNLOCK, PASSWORD_UNLOCK,13 PATTERN_UNLOCK, FINGERPRINT_UNLOCK } from './unlock-helpers';14import { EOL } from 'os';15const PACKAGE_INSTALL_TIMEOUT = 90000; // milliseconds16const CHROME_BROWSER_PACKAGE_ACTIVITY = {17 chrome: {18 pkg: 'com.android.chrome',19 activity: 'com.google.android.apps.chrome.Main',20 },21 chromium: {22 pkg: 'org.chromium.chrome.shell',23 activity: '.ChromeShellActivity',24 },25 chromebeta: {26 pkg: 'com.chrome.beta',27 activity: 'com.google.android.apps.chrome.Main',28 },29 browser: {30 pkg: 'com.android.browser',31 activity: 'com.android.browser.BrowserActivity',32 },33 'chromium-browser': {34 pkg: 'org.chromium.chrome',35 activity: 'com.google.android.apps.chrome.Main',36 },37 'chromium-webview': {38 pkg: 'org.chromium.webview_shell',39 activity: 'org.chromium.webview_shell.WebViewBrowserActivity',40 },41 default: {42 pkg: 'com.android.chrome',43 activity: 'com.google.android.apps.chrome.Main',44 },45};46const SETTINGS_HELPER_PKG_ID = 'io.appium.settings';47const SETTINGS_HELPER_MAIN_ACTIVITY = '.Settings';48const SETTINGS_HELPER_UNLOCK_ACTIVITY = '.Unlock';49let helpers = {};50helpers.createBaseADB = async function createBaseADB (opts = {}) {51 // filter out any unwanted options sent in52 // this list should be updated as ADB takes more arguments53 const {54 adbPort,55 suppressKillServer,56 remoteAdbHost,57 clearDeviceLogsOnStart,58 adbExecTimeout,59 useKeystore,60 keystorePath,61 keystorePassword,62 keyAlias,63 keyPassword,64 remoteAppsCacheLimit,65 buildToolsVersion,66 } = opts;67 return await ADB.createADB({68 adbPort,69 suppressKillServer,70 remoteAdbHost,71 clearDeviceLogsOnStart,72 adbExecTimeout,73 useKeystore,74 keystorePath,75 keystorePassword,76 keyAlias,77 keyPassword,78 remoteAppsCacheLimit,79 buildToolsVersion,80 });81};82helpers.getJavaVersion = async function getJavaVersion (logVersion = true) {83 let stderr;84 try {85 ({stderr} = await exec('java', ['-version']));86 } catch (e) {87 throw new Error(`Could not get the Java version. Is Java installed? Original error: ${e.stderr}`);88 }89 const versionMatch = /(java|openjdk)\s+version.+?([0-9._]+)/i.exec(stderr);90 if (!versionMatch) {91 throw new Error(`Could not parse Java version. Is Java installed? Original output: ${stderr}`);92 }93 if (logVersion) {94 logger.info(`Java version is: ${versionMatch[2]}`);95 }96 return versionMatch[2];97};98helpers.prepareEmulator = async function prepareEmulator (adb, opts) {99 let {100 avd,101 avdArgs,102 language,103 locale,104 avdLaunchTimeout,105 avdReadyTimeout,106 } = opts;107 if (!avd) {108 throw new Error('Cannot launch AVD without AVD name');109 }110 let avdName = avd.replace('@', '');111 let runningAVD = await adb.getRunningAVD(avdName);112 if (runningAVD !== null) {113 if (avdArgs && avdArgs.toLowerCase().indexOf('-wipe-data') > -1) {114 logger.debug(`Killing '${avdName}' because it needs to be wiped at start.`);115 await adb.killEmulator(avdName);116 } else {117 logger.debug('Not launching AVD because it is already running.');118 return;119 }120 }121 avdArgs = this.prepareAVDArgs(opts, adb, avdArgs);122 await adb.launchAVD(avd, avdArgs, language, locale, avdLaunchTimeout,123 avdReadyTimeout);124};125helpers.prepareAVDArgs = function prepareAVDArgs (opts, adb, avdArgs) {126 let args = avdArgs ? [avdArgs] : [];127 if (!_.isUndefined(opts.networkSpeed)) {128 let networkSpeed = this.ensureNetworkSpeed(adb, opts.networkSpeed);129 args.push('-netspeed', networkSpeed);130 }131 if (opts.isHeadless) {132 args.push('-no-window');133 }134 return args.join(' ');135};136helpers.ensureNetworkSpeed = function ensureNetworkSpeed (adb, networkSpeed) {137 if (_.values(adb.NETWORK_SPEED).indexOf(networkSpeed) !== -1) {138 return networkSpeed;139 }140 logger.warn(`Wrong network speed param ${networkSpeed}, using default: full. Supported values: ${_.values(adb.NETWORK_SPEED)}`);141 return adb.NETWORK_SPEED.FULL;142};143/**144 * Set and ensure the locale name of the device under test.145 *146 * @param {Object} adb - The adb module instance.147 * @param {string} language - Language. The language field is case insensitive, but Locale always canonicalizes to lower case.148 * format: [a-zA-Z]{2,8}. e.g. en, ja : https://developer.android.com/reference/java/util/Locale.html149 * @param {string} country - Country. The country (region) field is case insensitive, but Locale always canonicalizes to upper case.150 * format: [a-zA-Z]{2} | [0-9]{3}. e.g. US, JP : https://developer.android.com/reference/java/util/Locale.html151 * @param {?string} script - Script. The script field is case insensitive but Locale always canonicalizes to title case.152 * format: [a-zA-Z]{4}. e.g. Hans in zh-Hans-CN : https://developer.android.com/reference/java/util/Locale.html153 * @throws {Error} If it failed to set locale properly154 */155helpers.ensureDeviceLocale = async function ensureDeviceLocale (adb, language, country, script = null) {156 if (!_.isString(language) && !_.isString(country)) {157 logger.warn(`setDeviceLanguageCountry requires language or country.`);158 logger.warn(`Got language: '${language}' and country: '${country}'`);159 return;160 }161 await adb.setDeviceLanguageCountry(language, country, script);162 if (!await adb.ensureCurrentLocale(language, country, script)) {163 const message = script ? `language: ${language}, country: ${country} and script: ${script}` : `language: ${language} and country: ${country}`;164 throw new Error(`Failed to set ${message}`);165 }166};167helpers.getDeviceInfoFromCaps = async function getDeviceInfoFromCaps (opts = {}) {168 // we can create a throwaway ADB instance here, so there is no dependency169 // on instantiating on earlier (at this point, we have no udid)170 // we can only use this ADB object for commands that would not be confused171 // if multiple devices are connected172 const adb = await helpers.createBaseADB(opts);173 let udid = opts.udid;174 let emPort = null;175 // a specific avd name was given. try to initialize with that176 if (opts.avd) {177 await helpers.prepareEmulator(adb, opts);178 udid = adb.curDeviceId;179 emPort = adb.emulatorPort;180 } else {181 // no avd given. lets try whatever's plugged in devices/emulators182 logger.info('Retrieving device list');183 let devices = await adb.getDevicesWithRetry();184 // udid was given, lets try to init with that device185 if (udid) {186 if (!_.includes(_.map(devices, 'udid'), udid)) {187 logger.errorAndThrow(`Device ${udid} was not in the list of connected devices`);188 }189 emPort = adb.getPortFromEmulatorString(udid);190 } else if (opts.platformVersion) {191 opts.platformVersion = `${opts.platformVersion}`.trim();192 // a platform version was given. lets try to find a device with the same os193 const platformVersion = util.coerceVersion(opts.platformVersion, false);194 if (!platformVersion) {195 logger.errorAndThrow(`The provided platform version value '${platformVersion}' ` +196 `cannot be coerced to a valid version number`);197 }198 logger.info(`Looking for a device with Android '${opts.platformVersion}'`);199 // in case we fail to find something, give the user a useful log that has200 // the device udids and os versions so they know what's available201 const availDevices = [];202 let partialMatchCandidate = null;203 const extractVersionDigits = (versionStr) => {204 const match = /(\d+)\.?(\d+)?/.exec(versionStr);205 return match ? match.slice(1) : [];206 };207 const [majorPlatformVersion, minorPlatformVersion] = extractVersionDigits(platformVersion);208 // first try started devices/emulators209 for (const device of devices) {210 // direct adb calls to the specific device211 await adb.setDeviceId(device.udid);212 const rawDeviceOS = await adb.getPlatformVersion();213 availDevices.push(`${device.udid} (${rawDeviceOS})`);214 const deviceOS = util.coerceVersion(rawDeviceOS, false);215 if (!deviceOS) {216 continue;217 }218 if (util.compareVersions(deviceOS, '==', platformVersion)) {219 // Got an exact match - proceed immediately220 udid = device.udid;221 break;222 }223 const [majorDeviceVersion, minorDeviceVersion] = extractVersionDigits(deviceOS);224 if ((!opts.platformVersion.includes('.') && majorPlatformVersion === majorDeviceVersion)225 || (majorPlatformVersion === majorDeviceVersion && minorPlatformVersion === minorDeviceVersion)) {226 // Got a partial match - make sure we consider the most recent227 // device version available on the host system228 if (partialMatchCandidate229 && util.compareVersions(deviceOS, '>', _.values(partialMatchCandidate)[0])230 || !partialMatchCandidate) {231 partialMatchCandidate = {[device.udid]: deviceOS};232 }233 }234 }235 if (!udid && partialMatchCandidate) {236 udid = _.keys(partialMatchCandidate)[0];237 await adb.setDeviceId(udid);238 }239 if (!udid) {240 // we couldn't find anything! quit241 logger.errorAndThrow(`Unable to find an active device or emulator ` +242 `with OS ${opts.platformVersion}. The following are available: ` +243 availDevices.join(', '));244 }245 emPort = adb.getPortFromEmulatorString(udid);246 } else {247 // a udid was not given, grab the first device we see248 udid = devices[0].udid;249 emPort = adb.getPortFromEmulatorString(udid);250 }251 }252 logger.info(`Using device: ${udid}`);253 return {udid, emPort};254};255// returns a new adb instance with deviceId set256helpers.createADB = async function createADB (opts = {}) {257 const {udid, emPort} = opts;258 const adb = await helpers.createBaseADB(opts);259 adb.setDeviceId(udid);260 if (emPort) {261 adb.setEmulatorPort(emPort);262 }263 return adb;264};265helpers.validatePackageActivityNames = function validatePackageActivityNames (opts) {266 for (const key of ['appPackage', 'appActivity', 'appWaitPackage', 'appWaitActivity']) {267 const name = opts[key];268 if (!name) {269 continue;270 }271 const match = /([^\w.*,])+/.exec(name);272 if (!match) {273 continue;274 }275 logger.warn(`Capability '${key}' is expected to only include latin letters, digits, underscore, dot, comma and asterisk characters.`);276 logger.warn(`Current value '${name}' has non-matching character at index ${match.index}: '${name.substring(0, match.index + 1)}'`);277 }278};279helpers.getLaunchInfo = async function getLaunchInfo (adb, opts) {280 let {app, appPackage, appActivity, appWaitPackage, appWaitActivity} = opts;281 if (!app) {282 logger.warn('No app sent in, not parsing package/activity');283 return;284 }285 this.validatePackageActivityNames(opts);286 if (appPackage && appActivity) {287 return;288 }289 logger.debug('Parsing package and activity from app manifest');290 let {apkPackage, apkActivity} =291 await adb.packageAndLaunchActivityFromManifest(app);292 if (apkPackage && !appPackage) {293 appPackage = apkPackage;294 }295 if (!appWaitPackage) {296 appWaitPackage = appPackage;297 }298 if (apkActivity && !appActivity) {299 appActivity = apkActivity;300 }301 if (!appWaitActivity) {302 appWaitActivity = appActivity;303 }304 logger.debug(`Parsed package and activity are: ${apkPackage}/${apkActivity}`);305 return {appPackage, appWaitPackage, appActivity, appWaitActivity};306};307helpers.resetApp = async function resetApp (adb, opts = {}) {308 const {309 app,310 appPackage,311 fastReset,312 fullReset,313 androidInstallTimeout = PACKAGE_INSTALL_TIMEOUT,314 autoGrantPermissions,315 allowTestPackages316 } = opts;317 if (!appPackage) {318 throw new Error("'appPackage' option is required");319 }320 const isInstalled = await adb.isAppInstalled(appPackage);321 if (isInstalled) {322 try {323 await adb.forceStop(appPackage);324 } catch (ign) {}325 // fullReset has priority over fastReset326 if (!fullReset && fastReset) {327 const output = await adb.clear(appPackage);328 if (_.isString(output) && output.toLowerCase().includes('failed')) {329 throw new Error(`Cannot clear the application data of '${appPackage}'. Original error: ${output}`);330 }331 // executing `shell pm clear` resets previously assigned application permissions as well332 if (autoGrantPermissions) {333 try {334 await adb.grantAllPermissions(appPackage);335 } catch (error) {336 logger.error(`Unable to grant permissions requested. Original error: ${error.message}`);337 }338 }339 logger.debug(`Performed fast reset on the installed '${appPackage}' application (stop and clear)`);340 return;341 }342 }343 if (!app) {344 throw new Error("'app' option is required for reinstall");345 }346 logger.debug(`Running full reset on '${appPackage}' (reinstall)`);347 if (isInstalled) {348 await adb.uninstallApk(appPackage);349 }350 await adb.install(app, {351 grantPermissions: autoGrantPermissions,352 timeout: androidInstallTimeout,353 allowTestPackages,354 });355};356helpers.installApk = async function installApk (adb, opts = {}) {357 const {358 app,359 appPackage,360 fastReset,361 fullReset,362 androidInstallTimeout = PACKAGE_INSTALL_TIMEOUT,363 autoGrantPermissions,364 allowTestPackages,365 enforceAppInstall,366 } = opts;367 if (!app || !appPackage) {368 throw new Error("'app' and 'appPackage' options are required");369 }370 if (fullReset) {371 await this.resetApp(adb, opts);372 return;373 }374 const {375 appState,376 wasUninstalled377 } = await adb.installOrUpgrade(app, appPackage, {378 grantPermissions: autoGrantPermissions,379 timeout: androidInstallTimeout,380 allowTestPackages,381 enforceCurrentBuild: enforceAppInstall,382 });383 // There is no need to reset the newly installed app384 const isInstalledOverExistingApp = !wasUninstalled385 && appState !== adb.APP_INSTALL_STATE.NOT_INSTALLED;386 if (fastReset && isInstalledOverExistingApp) {387 logger.info(`Performing fast reset on '${appPackage}'`);388 await this.resetApp(adb, opts);389 }390};391/**392 * Installs an array of apks393 * @param {ADB} adb Instance of Appium ADB object394 * @param {Object} opts Opts defined in driver.js395 */396helpers.installOtherApks = async function installOtherApks (otherApps, adb, opts) {397 let {398 androidInstallTimeout = PACKAGE_INSTALL_TIMEOUT,399 autoGrantPermissions,400 allowTestPackages401 } = opts;402 // Install all of the APK's asynchronously403 await B.all(otherApps.map((otherApp) => {404 logger.debug(`Installing app: ${otherApp}`);405 return adb.installOrUpgrade(otherApp, null, {406 grantPermissions: autoGrantPermissions,407 timeout: androidInstallTimeout,408 allowTestPackages,409 });410 }));411};412/**413 * Uninstall an array of packages414 * @param {ADB} adb Instance of Appium ADB object415 * @param {Array<string>} appPackages An array of package names to uninstall. If this includes `'*'`, uninstall all of 3rd party apps416 * @param {Array<string>} filterPackages An array of packages does not uninstall when `*` is provided as `appPackages`417 */418helpers.uninstallOtherPackages = async function uninstallOtherPackages (adb, appPackages, filterPackages = []) {419 if (appPackages.includes('*')) {420 logger.debug('Uninstall third party packages');421 appPackages = await this.getThirdPartyPackages(adb, filterPackages);422 }423 logger.debug(`Uninstalling packages: ${appPackages}`);424 await B.all(appPackages.map((appPackage) => adb.uninstallApk(appPackage)));425};426/**427 * Get third party packages filtered with `filterPackages`428 * @param {ADB} adb Instance of Appium ADB object429 * @param {Array<string>} filterPackages An array of packages does not uninstall when `*` is provided as `appPackages`430 * @returns {Array<string>} An array of installed third pary packages431 */432helpers.getThirdPartyPackages = async function getThirdPartyPackages (adb, filterPackages = []) {433 try {434 const packagesString = await adb.shell(['pm', 'list', 'packages', '-3']);435 const appPackagesArray = packagesString.trim().replace(/package:/g, '').split(EOL);436 logger.debug(`'${appPackagesArray}' filtered with '${filterPackages}'`);437 return _.difference(appPackagesArray, filterPackages);438 } catch (err) {439 logger.warn(`Unable to get packages with 'adb shell pm list packages -3': ${err.message}`);440 return [];441 }442};443helpers.initUnicodeKeyboard = async function initUnicodeKeyboard (adb) {444 logger.debug('Enabling Unicode keyboard support');445 // get the default IME so we can return back to it later if we want446 let defaultIME = await adb.defaultIME();447 logger.debug(`Unsetting previous IME ${defaultIME}`);448 const appiumIME = `${SETTINGS_HELPER_PKG_ID}/.UnicodeIME`;449 logger.debug(`Setting IME to '${appiumIME}'`);450 await adb.enableIME(appiumIME);451 await adb.setIME(appiumIME);452 return defaultIME;453};454helpers.setMockLocationApp = async function setMockLocationApp (adb, app) {455 try {456 if (await adb.getApiLevel() < 23) {457 await adb.shell(['settings', 'put', 'secure', 'mock_location', '1']);458 } else {459 await adb.shell(['appops', 'set', app, 'android:mock_location', 'allow']);460 }461 } catch (err) {462 logger.warn(`Unable to set mock location for app '${app}': ${err.message}`);463 }464};465helpers.installHelperApp = async function installHelperApp (adb, apkPath, packageId) {466 // Sometimes adb push or adb instal take more time than expected to install an app467 // e.g. https://github.com/appium/io.appium.settings/issues/40#issuecomment-476593174468 await retry(2, async function retryInstallHelperApp () {469 await adb.installOrUpgrade(apkPath, packageId, {grantPermissions: true});470 });471};472/**473 * Pushes and installs io.appium.settings app.474 * Throws an error if the setting app is required475 *476 * @param {Adb} adb - The adb module instance.477 * @param {boolean} throwError[false] - Whether throw error or not478 * @throws {Error} If throwError is true and something happens in installation step479 */480helpers.pushSettingsApp = async function pushSettingsApp (adb, throwError = false) {481 logger.debug('Pushing settings apk to device...');482 try {483 await helpers.installHelperApp(adb, settingsApkPath, SETTINGS_HELPER_PKG_ID, throwError);484 } catch (err) {485 if (throwError) {486 throw err;487 }488 logger.warn(`Ignored error while installing '${settingsApkPath}': ` +489 `'${err.message}'. Features that rely on this helper ` +490 'require the apk such as toggle WiFi and getting location ' +491 'will raise an error if you try to use them.');492 }493 // Reinstall will stop the settings helper process anyway, so494 // there is no need to continue if the application is still running495 if (await adb.processExists(SETTINGS_HELPER_PKG_ID)) {496 logger.debug(`${SETTINGS_HELPER_PKG_ID} is already running. ` +497 `There is no need to reset its permissions.`);498 return;499 }500 if (await adb.getApiLevel() <= 23) { // Android 6- devices should have granted permissions501 // https://github.com/appium/appium/pull/11640#issuecomment-438260477502 logger.info('Granting android.permission.SET_ANIMATION_SCALE, CHANGE_CONFIGURATION, ACCESS_FINE_LOCATION by pm grant');503 await adb.grantPermissions(SETTINGS_HELPER_PKG_ID, [504 'android.permission.SET_ANIMATION_SCALE',505 'android.permission.CHANGE_CONFIGURATION',506 'android.permission.ACCESS_FINE_LOCATION'507 ]);508 }509 // launch io.appium.settings app due to settings failing to be set510 // if the app is not launched prior to start the session on android 7+511 // see https://github.com/appium/appium/issues/8957512 try {513 await adb.startApp({514 pkg: SETTINGS_HELPER_PKG_ID,515 activity: SETTINGS_HELPER_MAIN_ACTIVITY,516 action: 'android.intent.action.MAIN',517 category: 'android.intent.category.LAUNCHER',518 stopApp: false,519 waitForLaunch: false,520 });521 await waitForCondition(async () => await adb.processExists(SETTINGS_HELPER_PKG_ID), {522 waitMs: 5000,523 intervalMs: 300,524 });525 } catch (err) {526 const message = `Failed to launch Appium Settings app: ${err.message}`;527 err.message = message;528 logger.warn(message);529 if (throwError) {530 throw err;531 }532 }533};534/**535 * Extracts string.xml and converts it to string.json and pushes536 * it to /data/local/tmp/string.json on for use of bootstrap537 * If app is not present to extract string.xml it deletes remote strings.json538 * If app does not have strings.xml we push an empty json object to remote539 *540 * @param {?string} language - Language abbreviation, for example 'fr'. The default language541 * is used if this argument is not defined.542 * @param {Object} adb - The adb module instance.543 * @param {Object} opts - Driver options dictionary.544 * @returns {Object} The dictionary, where string resource identifiers are keys545 * along with their corresponding values for the given language or an empty object546 * if no matching resources were extracted.547 */548helpers.pushStrings = async function pushStrings (language, adb, opts) {549 const remoteDir = '/data/local/tmp';550 const stringsJson = 'strings.json';551 const remoteFile = path.posix.resolve(remoteDir, stringsJson);552 // clean up remote string.json if present553 await adb.rimraf(remoteFile);554 let app;555 try {556 app = opts.app || await adb.pullApk(opts.appPackage, opts.tmpDir);557 } catch (err) {558 logger.info(`Failed to pull an apk from '${opts.appPackage}' to '${opts.tmpDir}'. Original error: ${err.message}`);559 }560 if (_.isEmpty(opts.appPackage) || !(await fs.exists(app))) {561 logger.debug(`No app or package specified. Returning empty strings`);562 return {};563 }564 const stringsTmpDir = path.resolve(opts.tmpDir, opts.appPackage);565 try {566 logger.debug('Extracting strings from apk', app, language, stringsTmpDir);567 const {apkStrings, localPath} = await adb.extractStringsFromApk(app, language, stringsTmpDir);568 await adb.push(localPath, remoteDir);569 return apkStrings;570 } catch (err) {571 logger.warn(`Could not get strings, continuing anyway. Original error: ${err.message}`);572 await adb.shell('echo', [`'{}' > ${remoteFile}`]);573 } finally {574 await fs.rimraf(stringsTmpDir);575 }576 return {};577};578helpers.unlockWithUIAutomation = async function unlockWithUIAutomation (driver, adb, unlockCapabilities) {579 let unlockType = unlockCapabilities.unlockType;580 if (!unlocker.isValidUnlockType(unlockType)) {581 throw new Error(`Invalid unlock type ${unlockType}`);582 }583 let unlockKey = unlockCapabilities.unlockKey;584 if (!unlocker.isValidKey(unlockType, unlockKey)) {585 throw new Error(`Missing unlockKey ${unlockKey} capability for unlockType ${unlockType}`);586 }587 const unlockMethod = {588 [PIN_UNLOCK]: unlocker.pinUnlock,589 [PASSWORD_UNLOCK]: unlocker.passwordUnlock,590 [PATTERN_UNLOCK]: unlocker.patternUnlock,591 [FINGERPRINT_UNLOCK]: unlocker.fingerprintUnlock592 }[unlockType];593 await unlockMethod(adb, driver, unlockCapabilities);594};595helpers.unlockWithHelperApp = async function unlockWithHelperApp (adb) {596 logger.info('Unlocking screen');597 // Unlock succeed with a couple of retries.598 let firstRun = true;599 await retry(3, async function launchHelper () {600 // To reduce a time to call adb.isScreenLocked() since `adb shell dumpsys window` is easy to hang adb commands601 if (firstRun) {602 firstRun = false;603 } else {604 try {605 if (!(await adb.isScreenLocked())) {606 return;607 }608 } catch (e) {609 logger.warn(`Error in isScreenLocked: ${e.message}`);610 logger.warn('"adb shell dumpsys window" command has timed out.');611 logger.warn('The reason of this timeout is the delayed adb response. Resetting adb server can improve it.');612 }613 }614 logger.info(`Launching ${SETTINGS_HELPER_UNLOCK_ACTIVITY}`);615 await adb.shell([616 'am', 'start',617 '-n', `${SETTINGS_HELPER_PKG_ID}/${SETTINGS_HELPER_UNLOCK_ACTIVITY}`,618 '-c', 'android.intent.category.LAUNCHER',619 '-a', 'android.intent.action.MAIN',620 '-f', '0x10200000',621 ]);622 await B.delay(1000);623 });624};625helpers.unlock = async function unlock (driver, adb, capabilities) {626 if (!(await adb.isScreenLocked())) {627 logger.info('Screen already unlocked, doing nothing');628 return;629 }630 logger.debug('Screen is locked, trying to unlock');631 if (_.isUndefined(capabilities.unlockType)) {632 logger.warn('Using app unlock, this is going to be deprecated!');633 await helpers.unlockWithHelperApp(adb);634 } else {635 await helpers.unlockWithUIAutomation(driver, adb, {unlockType: capabilities.unlockType, unlockKey: capabilities.unlockKey});636 await helpers.verifyUnlock(adb);637 }638};639helpers.verifyUnlock = async function verifyUnlock (adb) {640 await retryInterval(2, 1000, async () => {641 if (await adb.isScreenLocked()) {642 throw new Error('Screen did not unlock successfully, retrying');643 }644 logger.debug('Screen unlocked successfully');645 });646};647helpers.initDevice = async function initDevice (adb, opts) {648 if (opts.skipDeviceInitialization) {649 logger.info(`'skipDeviceInitialization' is set. Skipping device initialization.`);650 } else {651 await adb.waitForDevice();652 // pushSettingsApp required before calling ensureDeviceLocale for API Level 24+653 // Some feature such as location/wifi are not necessary for all users,654 // but they require the settings app. So, try to configure it while Appium655 // does not throw error even if they fail.656 const shouldThrowError = opts.language657 || opts.locale658 || opts.localeScript659 || opts.unicodeKeyboard660 || opts.disableWindowAnimation661 || !opts.skipUnlock;662 await helpers.pushSettingsApp(adb, shouldThrowError);663 }664 if (!opts.avd) {665 await helpers.setMockLocationApp(adb, SETTINGS_HELPER_PKG_ID);666 }667 if (opts.language || opts.locale) {668 await helpers.ensureDeviceLocale(adb, opts.language, opts.locale, opts.localeScript);669 }670 if (opts.skipLogcatCapture) {671 logger.info(`'skipLogcatCapture' is set. Skipping starting logcat capture.`);672 } else {673 await adb.startLogcat();674 }675 if (opts.unicodeKeyboard) {676 return await helpers.initUnicodeKeyboard(adb);677 }678};679helpers.removeNullProperties = function removeNullProperties (obj) {680 for (let key of _.keys(obj)) {681 if (_.isNull(obj[key]) || _.isUndefined(obj[key])) {682 delete obj[key];683 }684 }685};686helpers.truncateDecimals = function truncateDecimals (number, digits) {687 let multiplier = Math.pow(10, digits),688 adjustedNum = number * multiplier,689 truncatedNum = Math[adjustedNum < 0 ? 'ceil' : 'floor'](adjustedNum);690 return truncatedNum / multiplier;691};692helpers.isChromeBrowser = function isChromeBrowser (browser) {693 return _.includes(Object.keys(CHROME_BROWSER_PACKAGE_ACTIVITY), (browser || '').toLowerCase());694};695helpers.getChromePkg = function getChromePkg (browser) {696 return CHROME_BROWSER_PACKAGE_ACTIVITY[browser.toLowerCase()] || CHROME_BROWSER_PACKAGE_ACTIVITY.default;697};698helpers.removeAllSessionWebSocketHandlers = async function removeAllSessionWebSocketHandlers (server, sessionId) {699 if (!server || !_.isFunction(server.getWebSocketHandlers)) {700 return;701 }702 const activeHandlers = await server.getWebSocketHandlers(sessionId);703 for (const pathname of _.keys(activeHandlers)) {704 await server.removeWebSocketHandler(pathname);705 }706};707/**708 * Takes a desired capability and tries to JSON.parse it as an array,709 * and either returns the parsed array or a singleton array.710 *711 * @param {any} cap A desired capability712 */713helpers.parseArray = function parseArray (cap) {714 let parsedCaps;715 try {716 parsedCaps = JSON.parse(cap);717 } catch (ign) { }718 if (_.isArray(parsedCaps)) {719 return parsedCaps;720 } else if (_.isString(cap)) {721 return [cap];722 }723 throw new Error(`must provide a string or JSON Array; received ${cap}`);724};725/**726 * Validate desired capabilities. Returns true if capability is valid727 *728 * @param {*} cap A desired capability729 * @return {boolean} Returns true if the capability is valid730 * @throws {Error} If the caps has invalid capability731 */732helpers.validateDesiredCaps = function validateDesiredCaps (caps) {733 if (caps.browserName) {734 if (caps.app) {735 // warn if the capabilities have both `app` and `browser, although this is common with selenium grid736 logger.warn(`The desired capabilities should generally not include both an 'app' and a 'browserName'`);737 }738 if (caps.appPackage) {739 logger.errorAndThrow(`The desired should not include both of an 'appPackage' and a 'browserName'`);740 }741 }742 if (caps.uninstallOtherPackages) {743 try {744 this.parseArray(caps.uninstallOtherPackages);745 } catch (e) {746 logger.errorAndThrow(`Could not parse "uninstallOtherPackages" capability: ${e.message}`);747 }748 }749 return true;750};751helpers.bootstrap = Bootstrap;752helpers.unlocker = unlocker;753export { helpers, SETTINGS_HELPER_PKG_ID };...
ah1.js
Source:ah1.js
1import _ from 'lodash';2import path from 'path';3import { exec } from 'teen_process';4import { retry, retryInterval } from 'asyncbox';5import logger from './logger';6import { fs } from 'appium-support';7import { path as unicodeIMEPath } from 'appium-android-ime';8import { path as settingsApkPath } from 'io.appium.settings';9import { path as unlockApkPath } from 'appium-unlock';10import Bootstrap from 'appium-android-bootstrap';11import B from 'bluebird';12import ADB from 'appium-adb';13import { default as unlocker, PIN_UNLOCK, PASSWORD_UNLOCK,14 PATTERN_UNLOCK, FINGERPRINT_UNLOCK } from './unlock-helpers';15const PACKAGE_INSTALL_TIMEOUT = 90000; // milliseconds16const CHROME_BROWSER_PACKAGE_ACTIVITY = {17 chrome: {18 pkg: 'com.android.chrome',19 activity: 'com.google.android.apps.chrome.Main',20 },21 chromium: {22 pkg: 'org.chromium.chrome.shell',23 activity: '.ChromeShellActivity',24 },25 chromebeta: {26 pkg: 'com.chrome.beta',27 activity: 'com.google.android.apps.chrome.Main',28 },29 browser: {30 pkg: 'com.android.browser',31 activity: 'com.android.browser.BrowserActivity',32 },33 'chromium-browser': {34 pkg: 'org.chromium.chrome',35 activity: 'com.google.android.apps.chrome.Main',36 },37 'chromium-webview': {38 pkg: 'org.chromium.webview_shell',39 activity: 'org.chromium.webview_shell.WebViewBrowserActivity',40 },41 default: {42 pkg: 'com.android.chrome',43 activity: 'com.google.android.apps.chrome.Main',44 },45};46const SETTINGS_HELPER_PKG_ID = 'io.appium.settings';47const SETTINGS_HELPER_PKG_ACTIVITY = ".Settings";48const UNLOCK_HELPER_PKG_ID = 'io.appium.unlock';49const UNLOCK_HELPER_PKG_ACTIVITY = ".Unlock";50const UNICODE_IME_PKG_ID = 'io.appium.android.ime';51let helpers = {};52helpers.createBaseADB = async function (opts = {}) {53 // filter out any unwanted options sent in54 // this list should be updated as ADB takes more arguments55 const {56 javaVersion,57 adbPort,58 suppressKillServer,59 remoteAdbHost,60 clearDeviceLogsOnStart,61 adbExecTimeout,62 } = opts;63 return await ADB.createADB({64 javaVersion,65 adbPort,66 suppressKillServer,67 remoteAdbHost,68 clearDeviceLogsOnStart,69 adbExecTimeout,70 });71};72helpers.parseJavaVersion = function (stderr) {73 let lines = stderr.split("\n");74 for (let line of lines) {75 if (new RegExp(/(java|openjdk) version/).test(line)) {76 return line.split(" ")[2].replace(/"/g, '');77 }78 }79 return null;80};81helpers.getJavaVersion = async function (logVersion = true) {82 let {stderr} = await exec('java', ['-version']);83 let javaVer = helpers.parseJavaVersion(stderr);84 if (javaVer === null) {85 throw new Error("Could not get the Java version. Is Java installed?");86 }87 if (logVersion) {88 logger.info(`Java version is: ${javaVer}`);89 }90 return javaVer;91};92helpers.prepareEmulator = async function (adb, opts) {93 let {avd, avdArgs, language, locale, avdLaunchTimeout,94 avdReadyTimeout} = opts;95 if (!avd) {96 throw new Error("Cannot launch AVD without AVD name");97 }98 let avdName = avd.replace('@', '');99 let runningAVD = await adb.getRunningAVD(avdName);100 if (runningAVD !== null) {101 if (avdArgs && avdArgs.toLowerCase().indexOf("-wipe-data") > -1) {102 logger.debug(`Killing '${avdName}' because it needs to be wiped at start.`);103 await adb.killEmulator(avdName);104 } else {105 logger.debug("Not launching AVD because it is already running.");106 return;107 }108 }109 avdArgs = this.prepareAVDArgs(opts, adb, avdArgs);110 await adb.launchAVD(avd, avdArgs, language, locale, avdLaunchTimeout,111 avdReadyTimeout);112};113helpers.prepareAVDArgs = function (opts, adb, avdArgs) {114 let args = avdArgs ? [avdArgs] : [];115 if (!_.isUndefined(opts.networkSpeed)) {116 let networkSpeed = this.ensureNetworkSpeed(adb, opts.networkSpeed);117 args.push('-netspeed', networkSpeed);118 }119 if (opts.isHeadless) {120 args.push('-no-window');121 }122 return args.join(' ');123};124helpers.ensureNetworkSpeed = function (adb, networkSpeed) {125 if (_.values(adb.NETWORK_SPEED).indexOf(networkSpeed) !== -1) {126 return networkSpeed;127 }128 logger.warn(`Wrong network speed param ${networkSpeed}, using default: full. Supported values: ${_.values(adb.NETWORK_SPEED)}`);129 return adb.NETWORK_SPEED.FULL;130};131helpers.ensureDeviceLocale = async function (adb, language, country) {132 if (!_.isString(language) && !_.isString(country)) {133 logger.warn(`setDeviceLanguageCountry requires language or country.`);134 logger.warn(`Got language: '${language}' and country: '${country}'`);135 return;136 }137 await adb.setDeviceLanguageCountry(language, country);138 if (!await adb.ensureCurrentLocale(language, country)) {139 throw new Error(`Failed to set language: ${language} and country: ${country}`);140 }141};142helpers.getDeviceInfoFromCaps = async function (opts = {}) {143 // we can create a throwaway ADB instance here, so there is no dependency144 // on instantiating on earlier (at this point, we have no udid)145 // we can only use this ADB object for commands that would not be confused146 // if multiple devices are connected147 const adb = await helpers.createBaseADB(opts);148 let udid = opts.udid;149 let emPort = null;150 // a specific avd name was given. try to initialize with that151 if (opts.avd) {152 await helpers.prepareEmulator(adb, opts);153 udid = adb.curDeviceId;154 emPort = adb.emulatorPort;155 } else {156 // no avd given. lets try whatever's plugged in devices/emulators157 logger.info("Retrieving device list");158 let devices = await adb.getDevicesWithRetry();159 // udid was given, lets try to init with that device160 if (udid) {161 if (!_.includes(_.map(devices, 'udid'), udid)) {162 logger.errorAndThrow(`Device ${udid} was not in the list ` +163 `of connected devices`);164 }165 emPort = adb.getPortFromEmulatorString(udid);166 } else if (opts.platformVersion) {167 opts.platformVersion = `${opts.platformVersion}`.trim();168 // a platform version was given. lets try to find a device with the same os169 logger.info(`Looking for a device with Android '${opts.platformVersion}'`);170 // in case we fail to find something, give the user a useful log that has171 // the device udids and os versions so they know what's available172 let availDevicesStr = [];173 // first try started devices/emulators174 for (let device of devices) {175 // direct adb calls to the specific device176 await adb.setDeviceId(device.udid);177 let deviceOS = await adb.getPlatformVersion();178 // build up our info string of available devices as we iterate179 availDevicesStr.push(`${device.udid} (${deviceOS})`);180 // we do a begins with check for implied wildcard matching181 // eg: 4 matches 4.1, 4.0, 4.1.3-samsung, etc182 if (deviceOS.indexOf(opts.platformVersion) === 0) {183 udid = device.udid;184 break;185 }186 }187 // we couldn't find anything! quit188 if (!udid) {189 logger.errorAndThrow(`Unable to find an active device or emulator ` +190 `with OS ${opts.platformVersion}. The following ` +191 `are available: ` + availDevicesStr.join(', '));192 }193 emPort = adb.getPortFromEmulatorString(udid);194 } else {195 // a udid was not given, grab the first device we see196 udid = devices[0].udid;197 emPort = adb.getPortFromEmulatorString(udid);198 }199 }200 logger.info(`Using device: ${udid}`);201 return {udid, emPort};202};203// returns a new adb instance with deviceId set204helpers.createADB = async function (opts = {}) {205 const {udid, emPort} = opts;206 const adb = await helpers.createBaseADB(opts);207 adb.setDeviceId(udid);208 if (emPort) {209 adb.setEmulatorPort(emPort);210 }211 return adb;212};213helpers.validatePackageActivityNames = function (opts) {214 for (const key of ['appPackage', 'appActivity', 'appWaitPackage', 'appWaitActivity']) {215 const name = opts[key];216 if (!name) {217 continue;218 }219 const match = /([^\w.*,])+/.exec(name);220 if (!match) {221 continue;222 }223 logger.warn(`Capability '${key}' is expected to only include latin letters, digits, underscore, dot, comma and asterisk characters.`);224 logger.warn(`Current value '${name}' has non-matching character at index ${match.index}: '${name.substring(0, match.index + 1)}'`);225 }226};227helpers.getLaunchInfo = async function (adb, opts) {228 let {app, appPackage, appActivity, appWaitPackage, appWaitActivity} = opts;229 if (!app) {230 logger.warn("No app sent in, not parsing package/activity");231 return;232 }233 this.validatePackageActivityNames(opts);234 if (appPackage && appActivity) {235 return;236 }237 logger.debug("Parsing package and activity from app manifest");238 let {apkPackage, apkActivity} =239 await adb.packageAndLaunchActivityFromManifest(app);240 if (apkPackage && !appPackage) {241 appPackage = apkPackage;242 }243 if (!appWaitPackage) {244 appWaitPackage = appPackage;245 }246 if (apkActivity && !appActivity) {247 appActivity = apkActivity;248 }249 if (!appWaitActivity) {250 appWaitActivity = appActivity;251 }252 logger.debug(`Parsed package and activity are: ${apkPackage}/${apkActivity}`);253 return {appPackage, appWaitPackage, appActivity, appWaitActivity};254};255helpers.resetApp = async function (adb, opts = {}) {256 const {257 app,258 appPackage,259 fastReset,260 fullReset,261 androidInstallTimeout = PACKAGE_INSTALL_TIMEOUT,262 autoGrantPermissions,263 allowTestPackages264 } = opts;265 if (!appPackage) {266 throw new Error("'appPackage' option is required");267 }268 const isInstalled = await adb.isAppInstalled(appPackage);269 if (isInstalled) {270 try {271 await adb.forceStop(appPackage);272 } catch (ign) {}273 // fullReset has priority over fastReset274 if (!fullReset && fastReset) {275 const output = await adb.clear(appPackage);276 if (_.isString(output) && output.toLowerCase().includes('failed')) {277 throw new Error(`Cannot clear the application data of '${appPackage}'. Original error: ${output}`);278 }279 // executing `shell pm clear` resets previously assigned application permissions as well280 if (autoGrantPermissions) {281 try {282 await adb.grantAllPermissions(appPackage);283 } catch (error) {284 logger.error(`Unable to grant permissions requested. Original error: ${error.message}`);285 }286 }287 logger.debug(`Performed fast reset on the installed '${appPackage}' application (stop and clear)`);288 return;289 }290 }291 if (!app) {292 throw new Error("'app' option is required for reinstall");293 }294 logger.debug(`Running full reset on '${appPackage}' (reinstall)`);295 if (isInstalled) {296 await adb.uninstallApk(appPackage);297 }298 await adb.install(app, {299 grantPermissions: autoGrantPermissions,300 timeout: androidInstallTimeout,301 allowTestPackages,302 });303};304helpers.installApk = async function (adb, opts = {}) {305 const {306 app,307 appPackage,308 fastReset,309 fullReset,310 androidInstallTimeout = PACKAGE_INSTALL_TIMEOUT,311 autoGrantPermissions,312 allowTestPackages313 } = opts;314 if (!app || !appPackage) {315 throw new Error("'app' and 'appPackage' options are required");316 }317 if (fullReset) {318 await this.resetApp(adb, opts);319 return;320 }321 // There is no need to reset the newly installed app322 const shouldPerformFastReset = fastReset && await adb.isAppInstalled(appPackage);323 await adb.installOrUpgrade(app, appPackage, {324 grantPermissions: autoGrantPermissions,325 timeout: androidInstallTimeout,326 allowTestPackages,327 });328 if (shouldPerformFastReset) {329 logger.info(`Performing fast reset on '${appPackage}'`);330 await this.resetApp(adb, opts);331 }332};333/**334 * Installs an array of apks335 * @param {ADB} adb Instance of Appium ADB object336 * @param {Object} opts Opts defined in driver.js337 */338helpers.installOtherApks = async function (otherApps, adb, opts) {339 let {340 androidInstallTimeout = PACKAGE_INSTALL_TIMEOUT,341 autoGrantPermissions,342 allowTestPackages343 } = opts;344 // Install all of the APK's asynchronously345 await B.all(otherApps.map((otherApp) => {346 logger.debug(`Installing app: ${otherApp}`);347 return adb.installOrUpgrade(otherApp, null, {348 grantPermissions: autoGrantPermissions,349 timeout: androidInstallTimeout,350 allowTestPackages,351 });352 }));353};354helpers.initUnicodeKeyboard = async function (adb) {355 logger.debug('Enabling Unicode keyboard support');356 logger.debug("Pushing unicode ime to device...");357 try {358 await adb.install(unicodeIMEPath, {replace: false});359 } catch (err) {360 logger.info(`Performing full reinstall of ${UNICODE_IME_PKG_ID} as a possible fix for: ${err.message}`);361 await adb.uninstallApk(UNICODE_IME_PKG_ID);362 await adb.install(unicodeIMEPath, {replace: false});363 }364 // get the default IME so we can return back to it later if we want365 let defaultIME = await adb.defaultIME();366 logger.debug(`Unsetting previous IME ${defaultIME}`);367 const appiumIME = `${UNICODE_IME_PKG_ID}/.UnicodeIME`;368 logger.debug(`Setting IME to '${appiumIME}'`);369 await adb.enableIME(appiumIME);370 await adb.setIME(appiumIME);371 return defaultIME;372};373helpers.setMockLocationApp = async function (adb, app) {374 try {375 if (await adb.getApiLevel() < 23) {376 await adb.shell(['settings', 'put', 'secure', 'mock_location', '1']);377 } else {378 await adb.shell(['appops', 'set', app, 'android:mock_location', 'allow']);379 }380 } catch (err) {381 logger.warn(`Unable to set mock location for app '${app}': ${err.message}`);382 }383};384helpers.installHelperApp = async function (adb, apkPath, packageId, appName) {385 try {386 await adb.installOrUpgrade(apkPath, packageId, {grantPermissions: true});387 } catch (err) {388 logger.warn(`Ignored error while installing Appium ${appName} helper: ` +389 `'${err.message}'. Manually uninstalling the application ` +390 `with package id '${packageId}' may help. Expect some Appium ` +391 `features may not work as expected unless this problem is ` +392 `fixed.`);393 }394};395helpers.pushSettingsApp = async function (adb, throwError = false) {396 logger.debug("Pushing settings apk to device...");397 await helpers.installHelperApp(adb, settingsApkPath, SETTINGS_HELPER_PKG_ID, 'Settings');398 // Reinstall will stop the settings helper process anyway, so399 // there is no need to continue if the application is still running400 if (await adb.processExists(SETTINGS_HELPER_PKG_ID)) {401 logger.debug(`${SETTINGS_HELPER_PKG_ID} is already running. ` +402 `There is no need to reset its permissions.`);403 return;404 }405 // lauch io.appium.settings app due to settings failing to be set406 // if the app is not launched prior to start the session on android 7+407 // see https://github.com/appium/appium/issues/8957408 try {409 await adb.startApp({410 pkg: SETTINGS_HELPER_PKG_ID,411 activity: SETTINGS_HELPER_PKG_ACTIVITY,412 action: "android.intent.action.MAIN",413 category: "android.intent.category.LAUNCHER",414 flags: "0x10200000",415 stopApp: false,416 });417 } catch (err) {418 logger.warn(`Failed to launch settings app: ${err.message}`);419 if (throwError) {420 throw err;421 }422 }423};424helpers.pushUnlock = async function (adb) {425 logger.debug("Pushing unlock helper app to device...");426 await helpers.installHelperApp(adb, unlockApkPath, UNLOCK_HELPER_PKG_ID, 'Unlock');427};428/**429 * Extracts string.xml and converts it to string.json and pushes430 * it to /data/local/tmp/string.json on for use of bootstrap431 * If app is not present to extract string.xml it deletes remote strings.json432 * If app does not have strings.xml we push an empty json object to remote433 *434 * @param {?string} language - Language abbreviation, for example 'fr'. The default language435 * is used if this argument is not defined.436 * @param {Object} adb - The adb mofdule instance.437 * @param {Object} opts - Driver options dictionary.438 * @returns {Object} The dictionary, where string resourtces identifiers are keys439 * along with their corresponding values for the given language or an empty object440 * if no matching resources were extracted.441 */442helpers.pushStrings = async function (language, adb, opts) {443 const remoteDir = '/data/local/tmp';444 const stringsJson = 'strings.json';445 const remoteFile = `${remoteDir}/${stringsJson}`;446 // clean up remote string.json if present447 await adb.rimraf(remoteFile);448 if (_.isEmpty(opts.appPackage) || !(await fs.exists(opts.app))) {449 return {};450 }451 const stringsTmpDir = path.resolve(opts.tmpDir, opts.appPackage);452 try {453 logger.debug('Extracting strings from apk', opts.app, language, stringsTmpDir);454 const {apkStrings, localPath} = await adb.extractStringsFromApk(opts.app, language, stringsTmpDir);455 await adb.push(localPath, remoteDir);456 return apkStrings;457 } catch (err) {458 logger.warn(`Could not get strings, continuing anyway. Original error: ${err.message}`);459 await adb.shell('echo', [`'{}' > ${remoteFile}`]);460 } finally {461 await fs.rimraf(stringsTmpDir);462 }463 return {};464};465helpers.unlockWithUIAutomation = async function (driver, adb, unlockCapabilities) {466 let unlockType = unlockCapabilities.unlockType;467 if (!unlocker.isValidUnlockType(unlockType)) {468 throw new Error(`Invalid unlock type ${unlockType}`);469 }470 let unlockKey = unlockCapabilities.unlockKey;471 if (!unlocker.isValidKey(unlockType, unlockKey)) {472 throw new Error(`Missing unlockKey ${unlockKey} capability for unlockType ${unlockType}`);473 }474 const unlockMethod = {475 [PIN_UNLOCK]: unlocker.pinUnlock,476 [PASSWORD_UNLOCK]: unlocker.passwordUnlock,477 [PATTERN_UNLOCK]: unlocker.patternUnlock,478 [FINGERPRINT_UNLOCK]: unlocker.fingerprintUnlock479 }[unlockType];480 await unlockMethod(adb, driver, unlockCapabilities);481};482helpers.unlockWithHelperApp = async function (adb) {483 logger.info("Unlocking screen");484 try {485 await adb.forceStop(UNLOCK_HELPER_PKG_ID);486 } catch (e) {487 // Sometimes we can see the below error, but we can ignore it.488 // [W3C] Encountered internal error running command: Error: Error executing adbExec. Original error: 'Command 'adb -P 5037 -s emulator-5554 shell am force-stop io.appium.unlock' timed out after 20000ms'; Stderr: ''; Code: 'null'489 logger.warn(`An error in unlockWithHelperApp: ${e.message}`);490 }491 let startOpts = {492 pkg: UNLOCK_HELPER_PKG_ID,493 activity: UNLOCK_HELPER_PKG_ACTIVITY,494 action: "android.intent.action.MAIN",495 category: "android.intent.category.LAUNCHER",496 flags: "0x10200000",497 stopApp: false,498 retry: false,499 waitDuration: 1000500 };501 // Unlock succeed with a couple of retries.502 let firstRun = true;503 await retry(3, async function () {504 // To reduce a time to call adb.isScreenLocked() since `adb shell dumpsys window` is easy to hang adb commands505 if (firstRun) {506 firstRun = false;507 } else {508 try {509 if (!(await adb.isScreenLocked())) {510 return;511 }512 } catch (e) {513 logger.warn(`Error in isScreenLocked: ${e.message}`);514 logger.warn("\"adb shell dumpsys window\" command has timed out.");515 logger.warn("The reason of this timeout is the delayed adb response. Resetting adb server can improve it.");516 }517 }518 logger.info(`Launching ${UNLOCK_HELPER_PKG_ID}`);519 // The command takes too much time so we should not call the command over twice continuously.520 await adb.startApp(startOpts);521 });522};523helpers.unlock = async function (driver, adb, capabilities) {524 if (!(await adb.isScreenLocked())) {525 logger.info("Screen already unlocked, doing nothing");526 return;527 }528 logger.debug("Screen is locked, trying to unlock");529 if (_.isUndefined(capabilities.unlockType)) {530 logger.warn("Using app unlock, this is going to be deprecated!");531 await helpers.unlockWithHelperApp(adb);532 } else {533 await helpers.unlockWithUIAutomation(driver, adb, {unlockType: capabilities.unlockType, unlockKey: capabilities.unlockKey});534 await helpers.verifyUnlock(adb);535 }536};537helpers.verifyUnlock = async function (adb) {538 await retryInterval(2, 1000, async () => {539 if (await adb.isScreenLocked()) {540 throw new Error("Screen did not unlock successfully, retrying");541 }542 logger.debug("Screen unlocked successfully");543 });544};545helpers.initDevice = async function (adb, opts) {546 await adb.waitForDevice();547 // pushSettingsApp required before calling ensureDeviceLocale for API Level 24+548 await helpers.pushSettingsApp(adb);549 if (!opts.avd) {550 await helpers.setMockLocationApp(adb, SETTINGS_HELPER_PKG_ID);551 }552 await helpers.ensureDeviceLocale(adb, opts.language, opts.locale);553 await adb.startLogcat();554 let defaultIME;555 if (opts.unicodeKeyboard) {556 defaultIME = await helpers.initUnicodeKeyboard(adb);557 }558 if (_.isUndefined(opts.unlockType)) {559 await helpers.pushUnlock(adb);560 }561 return defaultIME;562};563helpers.removeNullProperties = function (obj) {564 for (let key of _.keys(obj)) {565 if (_.isNull(obj[key]) || _.isUndefined(obj[key])) {566 delete obj[key];567 }568 }569};570helpers.truncateDecimals = function (number, digits) {571 let multiplier = Math.pow(10, digits),572 adjustedNum = number * multiplier,573 truncatedNum = Math[adjustedNum < 0 ? 'ceil' : 'floor'](adjustedNum);574 return truncatedNum / multiplier;575};576helpers.isChromeBrowser = function (browser) {577 return _.includes(Object.keys(CHROME_BROWSER_PACKAGE_ACTIVITY), (browser || '').toLowerCase());578};579helpers.getChromePkg = function (browser) {580 return CHROME_BROWSER_PACKAGE_ACTIVITY[browser.toLowerCase()] ||581 CHROME_BROWSER_PACKAGE_ACTIVITY.default;582};583helpers.removeAllSessionWebSocketHandlers = async function (server, sessionId) {584 if (!server || !_.isFunction(server.getWebSocketHandlers)) {585 return;586 }587 const activeHandlers = await server.getWebSocketHandlers(sessionId);588 for (const pathname of _.keys(activeHandlers)) {589 await server.removeWebSocketHandler(pathname);590 }591};592/**593 * Takes a desired capability and tries to JSON.parse it as an array,594 * and either returns the parsed array or a singleton array.595 *596 * @param {any} cap A desired capability597 */598helpers.parseArray = function (cap) {599 let parsedCaps;600 try {601 parsedCaps = JSON.parse(cap);602 } catch (ign) { }603 if (_.isArray(parsedCaps)) {604 return parsedCaps;605 } else if (_.isString(cap)) {606 return [cap];607 }608 throw new Error(`must provide a string or JSON Array; received ${cap}`);609};610helpers.validateDesiredCaps = function (caps) {611 // make sure that the capabilities have one of `app`, `appPackage` or `browser`612 if ((!caps.browserName || !this.isChromeBrowser(caps.browserName)) && !caps.app && !caps.appPackage) {613 logger.errorAndThrow('The desired capabilities must include either an app, appPackage or browserName');614 }615 if (caps.browserName) {616 if (caps.app) {617 // warn if the capabilities have both `app` and `browser, although this is common with selenium grid618 logger.warn('The desired capabilities should generally not include both an app and a browserName');619 }620 if (caps.appPackage) {621 logger.errorAndThrow(`The desired capabilities must include either 'appPackage' or 'browserName'`);622 }623 }624 return true;625};626helpers.bootstrap = Bootstrap;627helpers.unlocker = unlocker;...
utils.js
Source:utils.js
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';14const DEFAULT_TIMEOUT_KEY = 'default';15const XCTEST_LOG_FILES_PATTERNS = [16 /^Session-WebDriverAgentRunner.*\.log$/i,17 /^StandardOutputAndStandardError\.txt$/i,18];19const XCTEST_LOGS_CACHE_FOLDER_PREFIX = 'com.apple.dt.XCTest';20async function detectUdid () {21 log.debug('Auto-detecting real device udid...');22 const udids = await utilities.getConnectedDevices();23 if (_.isEmpty(udids)) {24 throw new Error('No device is connected to the host');25 }26 const udid = _.last(udids);27 if (udids.length > 1) {28 log.warn(`Multiple devices found: ${udids.join(', ')}`);29 log.warn(`Choosing '${udid}'. If this is wrong, manually set with 'udid' desired capability`);30 }31 log.debug(`Detected real device udid: '${udid}'`);32 return udid;33}34async function getAndCheckXcodeVersion () {35 let version;36 try {37 version = await xcode.getVersion(true);38 } catch (err) {39 log.debug(err);40 log.errorAndThrow(`Could not determine Xcode version: ${err.message}`);41 }42 // we do not support Xcodes < 7.3,43 if (version.versionFloat < 7.3) {44 log.errorAndThrow(`Xcode version '${version.versionString}'. Support for ` +45 `Xcode ${version.versionString} is not supported. ` +46 `Please upgrade to version 7.3 or higher`);47 }48 return version;49}50async function getAndCheckIosSdkVersion () {51 try {52 return await xcode.getMaxIOSSDK();53 } catch (err) {54 log.errorAndThrow(`Could not determine iOS SDK version: ${err.message}`);55 }56}57/**58 * Get the generic simulator for a given IOS version and device type (iPhone, iPad)59 *60 * @param {string|number} platformVersion IOS version. e.g.) 13.061 * @param {string} deviceName Type of IOS device. Can be iPhone, iPad (possibly more in the future)62 *63 * @returns {string} Generic iPhone or iPad simulator (if applicable)64 */65function getGenericSimulatorForIosVersion (platformVersion, deviceName) {66 let genericSimulators = iosGenericSimulators[deviceName];67 if (genericSimulators) {68 genericSimulators = genericSimulators.sort(([simOne], [simTwo]) => util.compareVersions(simOne, '<', simTwo) ? -1 : 1);69 // Find the highest iOS version in the list that is below the provided version70 let genericIosSimulator;71 for (const [platformVersionFromList, iosSimulator] of genericSimulators) {72 if (util.compareVersions(platformVersionFromList, '>', platformVersion)) {73 break;74 }75 genericIosSimulator = iosSimulator;76 }77 return genericIosSimulator;78 }79}80function translateDeviceName (platformVersion, deviceName = '') {81 const deviceNameTranslated = getGenericSimulatorForIosVersion(platformVersion, deviceName.toLowerCase().trim());82 if (deviceNameTranslated) {83 log.debug(`Changing deviceName from '${deviceName}' to '${deviceNameTranslated}'`);84 return deviceNameTranslated;85 }86 return deviceName;87}88// This map contains derived data logs folders as keys89// and values are the count of times the particular90// folder has been scheduled for removal91const derivedDataCleanupMarkers = new Map();92async function markSystemFilesForCleanup (wda) {93 if (!wda || !await wda.retrieveDerivedDataPath()) {94 log.warn('No WebDriverAgent derived data available, so unable to mark system files for cleanup');95 return;96 }97 const logsRoot = path.resolve(await wda.retrieveDerivedDataPath(), 'Logs');98 let markersCount = 0;99 if (derivedDataCleanupMarkers.has(logsRoot)) {100 markersCount = derivedDataCleanupMarkers.get(logsRoot);101 }102 derivedDataCleanupMarkers.set(logsRoot, ++markersCount);103}104async function clearSystemFiles (wda) {105 // only want to clear the system files for the particular WDA xcode run106 if (!wda || !await wda.retrieveDerivedDataPath()) {107 log.warn('No WebDriverAgent derived data available, so unable to clear system files');108 return;109 }110 const logsRoot = path.resolve(await wda.retrieveDerivedDataPath(), 'Logs');111 if (derivedDataCleanupMarkers.has(logsRoot)) {112 let markersCount = derivedDataCleanupMarkers.get(logsRoot);113 derivedDataCleanupMarkers.set(logsRoot, --markersCount);114 if (markersCount > 0) {115 log.info(`Not cleaning '${logsRoot}' folder, because the other session does not expect it to be cleaned`);116 return;117 }118 }119 derivedDataCleanupMarkers.set(logsRoot, 0);120 // Cleaning up big temporary files created by XCTest: https://github.com/appium/appium/issues/9410121 const globPattern = `${os.tmpdir()}/${XCTEST_LOGS_CACHE_FOLDER_PREFIX}*/`;122 const dstFolders = await fs.glob(globPattern);123 if (_.isEmpty(dstFolders)) {124 log.debug(`Did not find the temporary XCTest logs root at '${globPattern}'`);125 } else {126 // perform the cleanup asynchronously127 for (const dstFolder of dstFolders) {128 let scheduledFilesCount = 0;129 B.resolve(fs.walkDir(dstFolder, true, (itemPath, isDir) => {130 if (isDir) {131 return;132 }133 const fileName = path.basename(itemPath);134 if (!XCTEST_LOG_FILES_PATTERNS.some((p) => p.test(fileName))) {135 return;136 }137 // delete the file asynchronously138 fs.unlink(itemPath).catch((e) => {139 log.info(e.message);140 });141 scheduledFilesCount++;142 })).finally(() => {143 if (scheduledFilesCount > 0) {144 log.info(`Scheduled ${scheduledFilesCount} temporary XCTest log ` +145 `${util.pluralize('file', scheduledFilesCount)} for cleanup in '${dstFolder}'`);146 }147 }).catch((e) => {148 log.info(e.message);149 });150 }151 log.debug(`Started background XCTest logs cleanup in '${dstFolders}'`);152 }153 if (await fs.exists(logsRoot)) {154 log.info(`Cleaning test logs in '${logsRoot}' folder`);155 await iosUtils.clearLogs([logsRoot]);156 return;157 }158 log.info(`There is no ${logsRoot} folder, so not cleaning files`);159}160async function checkAppPresent (app) {161 log.debug(`Checking whether app '${app}' is actually present on file system`);162 if (!(await fs.exists(app))) {163 log.errorAndThrow(`Could not find app at '${app}'`);164 }165 log.debug('App is present');166}167async function getDriverInfo () {168 const stat = await fs.stat(path.resolve(__dirname, '..'));169 const built = stat.mtime.getTime();170 // get the package.json and the version from it171 const pkg = require(__filename.includes('build/lib/utils') ? '../../package.json' : '../package.json');172 const version = pkg.version;173 return {174 built,175 version,176 };177}178function normalizeCommandTimeouts (value) {179 // The value is normalized already180 if (typeof value !== 'string') {181 return value;182 }183 let result = {};184 // Use as default timeout for all commands if a single integer value is provided185 if (!isNaN(value)) {186 result[DEFAULT_TIMEOUT_KEY] = _.toInteger(value);187 return result;188 }189 // JSON object has been provided. Let's parse it190 try {191 result = JSON.parse(value);192 if (!_.isPlainObject(result)) {193 throw new Error();194 }195 } catch (err) {196 log.errorAndThrow(`"commandTimeouts" capability should be a valid JSON object. "${value}" was given instead`);197 }198 for (let [cmd, timeout] of _.toPairs(result)) {199 if (!_.isInteger(timeout) || timeout <= 0) {200 log.errorAndThrow(`The timeout for "${cmd}" should be a valid natural number of milliseconds. "${timeout}" was given instead`);201 }202 }203 return result;204}205async function printUser () {206 try {207 let {stdout} = await exec('whoami');208 log.debug(`Current user: '${stdout.trim()}'`);209 } catch (err) {210 log.debug(`Unable to get username running server: ${err.message}`);211 }212}213/**214 * Get the IDs of processes listening on the particular system port.215 * It is also possible to apply additional filtering based on the216 * process command line.217 *218 * @param {string|number} port - The port number.219 * @param {?Function} filteringFunc - Optional lambda function, which220 * receives command line string of the particular process221 * listening on given port, and is expected to return222 * either true or false to include/exclude the corresponding PID223 * from the resulting array.224 * @returns {Array<string>} - the list of matched process ids.225 */226async function getPIDsListeningOnPort (port, filteringFunc = null) {227 const result = [];228 try {229 // This only works since Mac OS X El Capitan230 const {stdout} = await exec('lsof', ['-ti', `tcp:${port}`]);231 result.push(...(stdout.trim().split(/\n+/)));232 } catch (e) {233 return result;234 }235 if (!_.isFunction(filteringFunc)) {236 return result;237 }238 return await B.filter(result, async (x) => {239 const {stdout} = await exec('ps', ['-p', x, '-o', 'command']);240 return await filteringFunc(stdout);241 });242}243/**244 * @typedef {Object} UploadOptions245 *246 * @property {?string} user - The name of the user for the remote authentication. Only works if `remotePath` is provided.247 * @property {?string} pass - The password for the remote authentication. Only works if `remotePath` is provided.248 * @property {?string} method - The http multipart upload method name. The 'PUT' one is used by default.249 * Only works if `remotePath` is provided.250 * @property {?Object} headers - Additional headers mapping for multipart http(s) uploads251 * @property {?string} fileFieldName [file] - The name of the form field, where the file content BLOB should be stored for252 * http(s) uploads253 * @property {?Object|Array<Pair>} formFields - Additional form fields for multipart http(s) uploads254 */255/**256 * Encodes the given local file to base64 and returns the resulting string257 * or uploads it to a remote server using http/https or ftp protocols258 * if `remotePath` is set259 *260 * @param {string} localPath - The path to an existing local file261 * @param {?string} remotePath - The path to the remote location, where262 * this file should be uploaded263 * @param {?UploadOptions} uploadOptions - Set of upload options264 * @returns {string} Either an empty string if the upload was successful or265 * base64-encoded file representation if `remotePath` is falsy266 */267async function encodeBase64OrUpload (localPath, remotePath = null, uploadOptions = {}) {268 if (!await fs.exists(localPath)) {269 log.errorAndThrow(`The file at '${localPath}' does not exist or is not accessible`);270 }271 if (_.isEmpty(remotePath)) {272 const {size} = await fs.stat(localPath);273 log.debug(`The size of the file is ${util.toReadableSizeString(size)}`);274 return (await util.toInMemoryBase64(localPath)).toString();275 }276 const {user, pass, method, headers, fileFieldName, formFields} = uploadOptions;277 const options = {278 method: method || 'PUT',279 headers,280 fileFieldName,281 formFields,282 };283 if (user && pass) {284 options.auth = {user, pass};285 }286 await net.uploadFile(localPath, remotePath, options);287 return '';288}289/**290 * Stops and removes all web socket handlers that are listening291 * in scope of the currect session.292 *293 * @param {Object} server - The instance of NodeJs HTTP server,294 * which hosts Appium295 * @param {string} sessionId - The id of the current session296 */297async function removeAllSessionWebSocketHandlers (server, sessionId) {298 if (!server || !_.isFunction(server.getWebSocketHandlers)) {299 return;300 }301 const activeHandlers = await server.getWebSocketHandlers(sessionId);302 for (const pathname of _.keys(activeHandlers)) {303 await server.removeWebSocketHandler(pathname);304 }305}306/**307 * @typedef {Object} PlatformOpts308 *309 * @property {boolean} isSimulator - Whether the destination platform is a Simulator310 * @property {boolean} isTvOS - Whether the destination platform is a Simulator311 */312/**313 * Verify whether the given application is compatible to the314 * platform where it is going to be installed and tested.315 *316 * @param {string} app - The actual path to the application bundle317 * @param {PlatformOpts} expectedPlatform318 * @throws {Error} If bundle architecture does not match the expected device architecture.319 */320async function verifyApplicationPlatform (app, expectedPlatform) {321 log.debug('Verifying application platform');322 const infoPlist = path.resolve(app, 'Info.plist');323 if (!await fs.exists(infoPlist)) {324 log.debug(`'${infoPlist}' does not exist`);325 return;326 }327 const {CFBundleSupportedPlatforms} = await plist.parsePlistFile(infoPlist);328 log.debug(`CFBundleSupportedPlatforms: ${JSON.stringify(CFBundleSupportedPlatforms)}`);329 if (!_.isArray(CFBundleSupportedPlatforms)) {330 log.debug(`CFBundleSupportedPlatforms key does not exist in '${infoPlist}'`);331 return;332 }333 const {334 isSimulator,335 isTvOS,336 } = expectedPlatform;337 const prefix = isTvOS ? 'AppleTV' : 'iPhone';338 const suffix = isSimulator ? 'Simulator' : 'OS';339 const dstPlatform = `${prefix}${suffix}`;340 if (!CFBundleSupportedPlatforms.includes(dstPlatform)) {341 throw new Error(`${isSimulator ? 'Simulator' : 'Real device'} architecture is unsupported by the '${app}' application. ` +342 `Make sure the correct deployment target has been selected for its compilation in Xcode.`);343 }344}345/**346 * Returns true if the urlString is localhost347 * @param {?string} urlString348 * @returns {boolean} Return true if the urlString is localhost349 */350function isLocalHost (urlString) {351 try {352 const {hostname} = url.parse(urlString);353 return ['localhost', '127.0.0.1', '::1', '::ffff:127.0.0.1'].includes(hostname);354 } catch (ign) {355 log.warn(`'${urlString}' cannot be parsed as a valid URL`);356 }357 return false;358}359/**360 * Normalizes platformVersion to a valid iOS version string361 *362 * @param {string} originalVersion - Loose version number, that can be parsed by semver363 * @return {string} iOS version number in <major>.<minor> format364 * @throws {Error} if the version number cannot be parsed365 */366function normalizePlatformVersion (originalVersion) {367 const normalizedVersion = semver.coerce(originalVersion);368 if (!normalizedVersion) {369 throw new Error(`The platform version '${originalVersion}' should be a valid version number`);370 }371 return `${normalizedVersion.major}.${normalizedVersion.minor}`;372}373export { detectUdid, getAndCheckXcodeVersion, getAndCheckIosSdkVersion, getGenericSimulatorForIosVersion,374 checkAppPresent, getDriverInfo,375 clearSystemFiles, translateDeviceName, normalizeCommandTimeouts,376 DEFAULT_TIMEOUT_KEY, markSystemFilesForCleanup, printUser,377 getPIDsListeningOnPort, encodeBase64OrUpload, removeAllSessionWebSocketHandlers,...
Using AI Code Generation
1var wd = require('wd');2var assert = require('assert');3var chai = require('chai');4var chaiAsPromised = require('chai-as-promised');5chai.use(chaiAsPromised);6var expect = chai.expect;7var should = chai.should();8var caps = {9}10var driver = wd.promiseChainRemote("localhost", 4723);11driver.init(caps).then(function () {12 return driver.execute("mobile: removeAllSessionWebSocketHandlers", [{13 }]);14}).then(function () {15}).then(function () {16 return driver.quit();17}).done();18var wd = require('wd');19var assert = require('assert');20var chai = require('chai');21var chaiAsPromised = require('chai-as-promised');22chai.use(chaiAsPromised);23var expect = chai.expect;24var should = chai.should();25var caps = {26}27var driver = wd.promiseChainRemote("localhost", 4723);28driver.init(caps).then(function () {29 return driver.execute("mobile: removeAllSessionWebSocketHandlers", [{30 }]);31}).then(function () {32}).then(function () {33 return driver.quit();34}).done();35var wd = require('wd');36var assert = require('assert');37var chai = require('chai');38var chaiAsPromised = require('chai-as-promised');39chai.use(chaiAsPromised);40var expect = chai.expect;41var should = chai.should();42var caps = {43}44var driver = wd.promiseChainRemote("localhost", 4723);
Using AI Code Generation
1const wdio = require('webdriverio');2const opts = {3 capabilities: {4 }5};6async function main () {7 const client = await wdio.remote(opts);8 await client.removeAllSessionWebSocketHandlers();9 await client.deleteSession();10}11main();
Using AI Code Generation
1const { remote } = require('webdriverio')2const opts = {3 capabilities: {4 }5}6const client = remote(opts)7async function main () {8 await client.deleteSession()9 await client.deleteSession()10}11main()12[HTTP] {}13[debug] [W3C (4e4d4f4a)] Calling AppiumDriver.deleteSession() with args: ["4e4d4f4a-4b4c-4d4e-4f50-4d4e4f505152"]14[debug] [BaseDriver] Event 'quitSessionRequested' logged at 1616580598199 (12:43:18 GMT+0530 (India Standard Time))15[debug] [W3C (4e4d4f4a)] Encountered internal error running command: Error: Could not proxy command to remote server. Original error: 404 - "Could not proxy. Proxy error: Could not find session 4e4d4f4a-4b4c-4d4e-4f50-4d4e4f505152"16[debug] [W3C (4e4d4f4a)] at JWProxy.command (/Users/username/.nvm/versions/node/v
Using AI Code Generation
1const wdio = require('webdriverio');2const opts = {3 capabilities: {4 }5};6async function main() {7 let client = await wdio.remote(opts);8 await client.execute('mobile: removeAllSessionWebSocketHandlers', {});9 await client.deleteSession();10}11main();
Using AI Code Generation
1var wd = require('wd');2var assert = require('assert');3var chai = require('chai');4var chaiAsPromised = require('chai-as-promised');5var caps = require('./caps');6var app = require('./app');7chai.use(chaiAsPromised);8chai.should();9chaiAsPromised.transferPromiseness = wd.transferPromiseness;10describe('XCUITest Driver', function() {11 this.timeout(300000);12 before(function(done) {13 var desired = caps.ios92;14 driver.init(desired).nodeify(done);15 });16 after(function(done) {17 driver.quit().nodeify(done);18 });19 it('should remove all session web socket handlers', function(done) {20 .removeAllSessionWebSocketHandlers()21 .nodeify(done);22 });23});24var caps = {25 ios92: {26 app: require('./app').iosTestApp27 }28};29module.exports = caps;30var app = {
Learn to execute automation testing from scratch with LambdaTest Learning Hub. Right from setting up the prerequisites to run your first automation test, to following best practices and diving deeper into advanced test scenarios. LambdaTest Learning Hubs compile a list of step-by-step guides to help you be proficient with different test automation frameworks i.e. Selenium, Cypress, TestNG etc.
You could also refer to video tutorials over LambdaTest YouTube channel to get step by step demonstration from industry experts.
Get 100 minutes of automation test minutes FREE!!