How to use helpers.initDevice method in Appium Android Driver

Best JavaScript code snippet using appium-android-driver

android-helper-specs.js

Source:android-helper-specs.js Github

copy

Full Screen

...699 mocks.adb.expects('startLogcat').once();700 mocks.helpers.expects('pushSettingsApp').once();701 mocks.helpers.expects('ensureDeviceLocale').withExactArgs(adb, opts.language, opts.locale, opts.localeScript).once();702 mocks.helpers.expects('setMockLocationApp').withExactArgs(adb, 'io.appium.settings').once();703 await helpers.initDevice(adb, opts);704 mocks.helpers.verify();705 mocks.adb.verify();706 });707 it('should init device without locale and language', async function () {708 const opts = {};709 mocks.adb.expects('waitForDevice').once();710 mocks.adb.expects('startLogcat').once();711 mocks.helpers.expects('pushSettingsApp').once();712 mocks.helpers.expects('ensureDeviceLocale').never();713 mocks.helpers.expects('setMockLocationApp').withExactArgs(adb, 'io.appium.settings').once();714 await helpers.initDevice(adb, opts);715 mocks.helpers.verify();716 mocks.adb.verify();717 });718 it('should init device with either locale or language', async function () {719 const opts = {language: 'en'};720 mocks.adb.expects('waitForDevice').once();721 mocks.adb.expects('startLogcat').once();722 mocks.helpers.expects('pushSettingsApp').once();723 mocks.helpers.expects('ensureDeviceLocale').withExactArgs(adb, opts.language, opts.locale, opts.localeScript).once();724 mocks.helpers.expects('setMockLocationApp').withExactArgs(adb, 'io.appium.settings').once();725 await helpers.initDevice(adb, opts);726 mocks.helpers.verify();727 mocks.adb.verify();728 });729 it('should not install mock location on emulator', async function () {730 const opts = {avd: 'avd'};731 mocks.adb.expects('waitForDevice').once();732 mocks.adb.expects('startLogcat').once();733 mocks.helpers.expects('pushSettingsApp').once();734 mocks.helpers.expects('ensureDeviceLocale').never();735 mocks.helpers.expects('setMockLocationApp').never();736 await helpers.initDevice(adb, opts);737 mocks.helpers.verify();738 mocks.adb.verify();739 });740 it('should return defaultIME if unicodeKeyboard is setted to true', async function () {741 const opts = {unicodeKeyboard: true};742 mocks.adb.expects('waitForDevice').once();743 mocks.adb.expects('startLogcat').once();744 mocks.helpers.expects('pushSettingsApp').once();745 mocks.helpers.expects('ensureDeviceLocale').never();746 mocks.helpers.expects('setMockLocationApp').once();747 mocks.helpers.expects('initUnicodeKeyboard').withExactArgs(adb).once().returns('defaultIME');748 await helpers.initDevice(adb, opts).should.become('defaultIME');749 mocks.helpers.verify();750 mocks.adb.verify();751 });752 it('should return undefined if unicodeKeyboard is setted to false', async function () {753 const opts = {unicodeKeyboard: false};754 mocks.adb.expects('waitForDevice').once();755 mocks.adb.expects('startLogcat').once();756 mocks.helpers.expects('pushSettingsApp').once();757 mocks.helpers.expects('ensureDeviceLocale').never();758 mocks.helpers.expects('setMockLocationApp').once();759 mocks.helpers.expects('initUnicodeKeyboard').never();760 should.not.exist(await helpers.initDevice(adb, opts));761 mocks.helpers.verify();762 mocks.adb.verify();763 });764 it('should not push unlock app if unlockType is defined', async function () {765 const opts = {unlockType: 'unlock_type'};766 mocks.adb.expects('waitForDevice').once();767 mocks.adb.expects('startLogcat').once();768 mocks.helpers.expects('pushSettingsApp').once();769 mocks.helpers.expects('ensureDeviceLocale').never();770 mocks.helpers.expects('setMockLocationApp').once();771 mocks.helpers.expects('initUnicodeKeyboard').never();772 await helpers.initDevice(adb, opts);773 mocks.helpers.verify();774 mocks.adb.verify();775 });776 it('should init device without starting logcat', async function () {777 const opts = { skipLogcatCapture: true };778 mocks.adb.expects('waitForDevice').once();779 mocks.adb.expects('startLogcat').never();780 mocks.helpers.expects('pushSettingsApp').once();781 mocks.helpers.expects('ensureDeviceLocale').never();782 mocks.helpers.expects('setMockLocationApp').withExactArgs(adb, 'io.appium.settings').once();783 await helpers.initDevice(adb, opts);784 mocks.helpers.verify();785 mocks.adb.verify();786 });787 }));788 describe('removeNullProperties', function () {789 it('should ignore null properties', function () {790 let test = {foo: null, bar: true};791 helpers.removeNullProperties(test);792 _.keys(test).length.should.equal(1);793 });794 it('should ignore undefined properties', function () {795 let test = {foo: undefined, bar: true};796 helpers.removeNullProperties(test);797 _.keys(test).length.should.equal(1);...

Full Screen

Full Screen

driver.js

Source:driver.js Github

copy

Full Screen

...285 this.caps.deviceName = this.adb.curDeviceId;286 this.caps.deviceUDID = this.opts.udid;287 // start an avd, set the language/locale, pick an emulator, etc...288 // TODO with multiple devices we'll need to parameterize this289 this.defaultIME = await helpers.initDevice(this.adb, this.opts);290 // Prepare the device by forwarding the UiAutomator2 port291 // This call mutates this.opts.systemPort if it is not set explicitly292 await this.allocateSystemPort();293 // set up the modified UiAutomator2 server etc294 await this.initUiAutomator2Server();295 // Should be after installing io.appium.settings in helpers.initDevice296 if (this.opts.disableWindowAnimation && (await this.adb.getApiLevel() < 26)) { // API level 26 is Android 8.0.297 // Granting android.permission.SET_ANIMATION_SCALE is necessary to handle animations under API level 26298 // Read https://github.com/appium/appium/pull/11640#issuecomment-438260477299 // `--no-window-animation` works over Android 8 to disable all of animations300 if (await this.adb.isAnimationOn()) {301 logger.info('Disabling animation via io.appium.settings');302 await this.adb.setAnimationState(false);303 this._wasWindowAnimationDisabled = true;...

Full Screen

Full Screen

ah1.js

Source:ah1.js Github

copy

Full Screen

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;...

Full Screen

Full Screen

android-helpers.js

Source:android-helpers.js Github

copy

Full Screen

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, PATTERN_UNLOCK, FINGERPRINT_UNLOCK } from './unlock-helpers';14const PACKAGE_INSTALL_TIMEOUT = 90000; // milliseconds15const CHROME_BROWSER_PACKAGE_ACTIVITY = {16 chrome: {17 pkg: 'com.android.chrome',18 activity: 'com.google.android.apps.chrome.Main',19 },20 chromium: {21 pkg: 'org.chromium.chrome.shell',22 activity: '.ChromeShellActivity',23 },24 chromebeta: {25 pkg: 'com.chrome.beta',26 activity: 'com.google.android.apps.chrome.Main',27 },28 browser: {29 pkg: 'com.android.browser',30 activity: 'com.android.browser.BrowserActivity',31 },32 'chromium-browser': {33 pkg: 'org.chromium.chrome',34 activity: 'com.google.android.apps.chrome.Main',35 },36 'chromium-webview': {37 pkg: 'org.chromium.webview_shell',38 activity: 'org.chromium.webview_shell.WebViewBrowserActivity',39 },40 default: {41 pkg: 'com.android.chrome',42 activity: 'com.google.android.apps.chrome.Main',43 },44};45const SETTINGS_HELPER_PKG_ID = 'io.appium.settings';46const SETTINGS_HELPER_PKG_ACTIVITY = ".Settings";47const UNLOCK_HELPER_PKG_ID = 'io.appium.unlock';48const UNLOCK_HELPER_PKG_ACTIVITY = ".Unlock";49let helpers = {};50helpers.parseJavaVersion = function (stderr) {51 let lines = stderr.split("\n");52 for (let line of lines) {53 if (new RegExp(/(java|openjdk) version/).test(line)) {54 return line.split(" ")[2].replace(/"/g, '');55 }56 }57 return null;58};59helpers.getJavaVersion = async function () {60 logger.debug("Getting Java version");61 let {stderr} = await exec('java', ['-version']);62 let javaVer = helpers.parseJavaVersion(stderr);63 if (javaVer === null) {64 throw new Error("Could not get the Java version. Is Java installed?");65 }66 logger.info(`Java version is: ${javaVer}`);67 return javaVer;68};69helpers.prepareEmulator = async function (adb, opts) {70 let {avd, avdArgs, language, locale, avdLaunchTimeout,71 avdReadyTimeout} = opts;72 if (!avd) {73 throw new Error("Cannot launch AVD without AVD name");74 }75 let avdName = avd.replace('@', '');76 let runningAVD = await adb.getRunningAVD(avdName);77 if (runningAVD !== null) {78 if (avdArgs && avdArgs.toLowerCase().indexOf("-wipe-data") > -1) {79 logger.debug(`Killing '${avdName}' because it needs to be wiped at start.`);80 await adb.killEmulator(avdName);81 } else {82 logger.debug("Not launching AVD because it is already running.");83 return;84 }85 }86 avdArgs = this.prepareAVDArgs(opts, adb, avdArgs);87 await adb.launchAVD(avd, avdArgs, language, locale, avdLaunchTimeout,88 avdReadyTimeout);89};90helpers.prepareAVDArgs = function (opts, adb, avdArgs) {91 let args = avdArgs ? [avdArgs] : [];92 if (!_.isUndefined(opts.networkSpeed)) {93 let networkSpeed = this.ensureNetworkSpeed(adb, opts.networkSpeed);94 args.push('-netspeed', networkSpeed);95 }96 if (opts.isHeadless) {97 args.push('-no-window');98 }99 return args.join(' ');100};101helpers.ensureNetworkSpeed = function (adb, networkSpeed) {102 if (_.values(adb.NETWORK_SPEED).indexOf(networkSpeed) !== -1) {103 return networkSpeed;104 }105 logger.warn(`Wrong network speed param ${networkSpeed}, using default: full. Supported values: ${_.values(adb.NETWORK_SPEED)}`);106 return adb.NETWORK_SPEED.FULL;107};108helpers.ensureDeviceLocale = async function (adb, language, country) {109 if (!_.isString(language) && !_.isString(country)) {110 logger.warn(`setDeviceLanguageCountry requires language or country.`);111 logger.warn(`Got language: '${language}' and country: '${country}'`);112 return;113 }114 await adb.setDeviceLanguageCountry(language, country);115 if (!await adb.ensureCurrentLocale(language, country)) {116 throw new Error(`Failed to set language: ${language} and country: ${country}`);117 }118};119helpers.getDeviceInfoFromCaps = async function (opts = {}) {120 // we can create a throwaway ADB instance here, so there is no dependency121 // on instantiating on earlier (at this point, we have no udid)122 // we can only use this ADB object for commands that would not be confused123 // if multiple devices are connected124 let adb = await ADB.createADB({125 javaVersion: opts.javaVersion,126 adbPort: opts.adbPort,127 remoteAdbHost: opts.remoteAdbHost,128 suppressKillServer: opts.suppressKillServer,129 clearDeviceLogsOnStart: opts.clearDeviceLogsOnStart,130 });131 let udid = opts.udid;132 let emPort = null;133 // a specific avd name was given. try to initialize with that134 if (opts.avd) {135 await helpers.prepareEmulator(adb, opts);136 udid = adb.curDeviceId;137 emPort = adb.emulatorPort;138 } else {139 // no avd given. lets try whatever's plugged in devices/emulators140 logger.info("Retrieving device list");141 let devices = await adb.getDevicesWithRetry();142 // udid was given, lets try to init with that device143 if (udid) {144 if (!_.includes(_.map(devices, 'udid'), udid)) {145 logger.errorAndThrow(`Device ${udid} was not in the list ` +146 `of connected devices`);147 }148 emPort = adb.getPortFromEmulatorString(udid);149 } else if (opts.platformVersion) {150 opts.platformVersion = `${opts.platformVersion}`.trim();151 // a platform version was given. lets try to find a device with the same os152 logger.info(`Looking for a device with Android '${opts.platformVersion}'`);153 // in case we fail to find something, give the user a useful log that has154 // the device udids and os versions so they know what's available155 let availDevicesStr = [];156 // first try started devices/emulators157 for (let device of devices) {158 // direct adb calls to the specific device159 await adb.setDeviceId(device.udid);160 let deviceOS = await adb.getPlatformVersion();161 // build up our info string of available devices as we iterate162 availDevicesStr.push(`${device.udid} (${deviceOS})`);163 // we do a begins with check for implied wildcard matching164 // eg: 4 matches 4.1, 4.0, 4.1.3-samsung, etc165 if (deviceOS.indexOf(opts.platformVersion) === 0) {166 udid = device.udid;167 break;168 }169 }170 // we couldn't find anything! quit171 if (!udid) {172 logger.errorAndThrow(`Unable to find an active device or emulator ` +173 `with OS ${opts.platformVersion}. The following ` +174 `are available: ` + availDevicesStr.join(', '));175 }176 emPort = adb.getPortFromEmulatorString(udid);177 } else {178 // a udid was not given, grab the first device we see179 udid = devices[0].udid;180 emPort = adb.getPortFromEmulatorString(udid);181 }182 }183 logger.info(`Using device: ${udid}`);184 return {udid, emPort};185};186// returns a new adb instance with deviceId set187helpers.createADB = async function (javaVersion, udid, emPort, adbPort, suppressKillServer, remoteAdbHost, clearDeviceLogsOnStart) {188 let adb = await ADB.createADB({189 javaVersion,190 adbPort,191 suppressKillServer,192 remoteAdbHost,193 clearDeviceLogsOnStart,194 });195 adb.setDeviceId(udid);196 if (emPort) {197 adb.setEmulatorPort(emPort);198 }199 return adb;200};201helpers.getLaunchInfo = async function (adb, opts) {202 let {app, appPackage, appActivity, appWaitPackage, appWaitActivity} = opts;203 if (!app) {204 logger.warn("No app sent in, not parsing package/activity");205 return;206 }207 if (appPackage && appActivity) {208 return;209 }210 logger.debug("Parsing package and activity from app manifest");211 let {apkPackage, apkActivity} =212 await adb.packageAndLaunchActivityFromManifest(app);213 if (apkPackage && !appPackage) {214 appPackage = apkPackage;215 }216 if (!appWaitPackage) {217 appWaitPackage = appPackage;218 }219 if (apkActivity && !appActivity) {220 appActivity = apkActivity;221 }222 if (!appWaitActivity) {223 appWaitActivity = appActivity;224 }225 logger.debug(`Parsed package and activity are: ${apkPackage}/${apkActivity}`);226 return {appPackage, appWaitPackage, appActivity, appWaitActivity};227};228helpers.resetApp = async function (adb, opts = {}) {229 const {app, appPackage, fastReset, fullReset,230 androidInstallTimeout = PACKAGE_INSTALL_TIMEOUT,231 autoGrantPermissions} = opts;232 if (!appPackage) {233 throw new Error("'appPackage' option is required");234 }235 const isInstalled = await adb.isAppInstalled(appPackage);236 if (isInstalled) {237 try {238 await adb.forceStop(appPackage);239 } catch (ign) {}240 // fullReset has priority over fastReset241 if (!fullReset && fastReset) {242 const output = await adb.clear(appPackage);243 if (_.isString(output) && output.toLowerCase().includes('failed')) {244 throw new Error(`Cannot clear the application data of '${appPackage}'. Original error: ${output}`);245 }246 // executing `shell pm clear` resets previously assigned application permissions as well247 if (autoGrantPermissions) {248 try {249 await adb.grantAllPermissions(appPackage);250 } catch (error) {251 logger.error(`Unable to grant permissions requested. Original error: ${error.message}`);252 }253 }254 logger.debug(`Performed fast reset on the installed '${appPackage}' application (stop and clear)`);255 return;256 }257 }258 if (!app) {259 throw new Error("'app' option is required for reinstall");260 }261 logger.debug(`Running full reset on '${appPackage}' (reinstall)`);262 if (isInstalled) {263 await adb.uninstallApk(appPackage);264 }265 await adb.install(app, {266 grantPermissions: autoGrantPermissions,267 timeout: androidInstallTimeout268 });269};270helpers.installApk = async function (adb, opts = {}) {271 const {app, appPackage, fastReset, fullReset,272 androidInstallTimeout = PACKAGE_INSTALL_TIMEOUT,273 autoGrantPermissions} = opts;274 if (!app || !appPackage) {275 throw new Error("'app' and 'appPackage' options are required");276 }277 if (fullReset) {278 await this.resetApp(adb, opts);279 return;280 }281 // There is no need to reset the newly installed app282 const shouldPerformFastReset = fastReset && await adb.isAppInstalled(appPackage);283 await adb.installOrUpgrade(app, appPackage, {284 grantPermissions: autoGrantPermissions,285 timeout: androidInstallTimeout286 });287 if (shouldPerformFastReset) {288 logger.info(`Performing fast reset on '${appPackage}'`);289 await this.resetApp(adb, opts);290 }291};292/**293 * Installs an array of apks294 * @param {ADB} adb Instance of Appium ADB object295 * @param {Object} opts Opts defined in driver.js296 */297helpers.installOtherApks = async function (otherApps, adb, opts) {298 let {299 androidInstallTimeout = PACKAGE_INSTALL_TIMEOUT,300 autoGrantPermissions301 } = opts;302 // Install all of the APK's asynchronously303 await B.all(otherApps.map((otherApp) => {304 logger.debug(`Installing app: ${otherApp}`);305 return adb.installOrUpgrade(otherApp, null, {306 grantPermissions: autoGrantPermissions,307 timeout: androidInstallTimeout,308 });309 }));310};311helpers.initUnicodeKeyboard = async function (adb) {312 logger.debug('Enabling Unicode keyboard support');313 logger.debug("Pushing unicode ime to device...");314 // await adb.install(unicodeIMEPath, {replace: false});315 // get the default IME so we can return back to it later if we want316 let defaultIME = await adb.defaultIME();317 logger.debug(`Unsetting previous IME ${defaultIME}`);318 const appiumIME = 'io.appium.android.ime/.UnicodeIME';319 logger.debug(`Setting IME to '${appiumIME}'`);320 await adb.enableIME(appiumIME);321 await adb.setIME(appiumIME);322 return defaultIME;323};324helpers.setMockLocationApp = async function (adb, app) {325 try {326 if (await adb.getApiLevel() < 23) {327 await adb.shell(['settings', 'put', 'secure', 'mock_location', '1']);328 } else {329 await adb.shell(['appops', 'set', app, 'android:mock_location', 'allow']);330 }331 } catch (err) {332 logger.warn(`Unable to set mock location for app '${app}': ${err.message}`);333 }334};335helpers.installHelperApp = async function (adb, apkPath, packageId, appName) {336 try {337 await adb.installOrUpgrade(apkPath, packageId, {grantPermissions: true});338 } catch (err) {339 logger.warn(`Ignored error while installing Appium ${appName} helper: ` +340 `'${err.message}'. Manually uninstalling the application ` +341 `with package id '${packageId}' may help. Expect some Appium ` +342 `features may not work as expected unless this problem is ` +343 `fixed.`);344 }345};346helpers.pushSettingsApp = async function (adb, throwError = false) {347 logger.debug("Pushing settings apk to device...");348 await helpers.installHelperApp(adb, settingsApkPath, SETTINGS_HELPER_PKG_ID, 'Settings');349 // Reinstall will stop the settings helper process anyway, so350 // there is no need to continue if the application is still running351 if (await adb.processExists(SETTINGS_HELPER_PKG_ID)) {352 logger.debug(`${SETTINGS_HELPER_PKG_ID} is already running. ` +353 `There is no need to reset its permissions.`);354 return;355 }356 // lauch io.appium.settings app due to settings failing to be set357 // if the app is not launched prior to start the session on android 7+358 // see https://github.com/appium/appium/issues/8957359 try {360 await adb.startApp({361 pkg: SETTINGS_HELPER_PKG_ID,362 activity: SETTINGS_HELPER_PKG_ACTIVITY,363 action: "android.intent.action.MAIN",364 category: "android.intent.category.LAUNCHER",365 flags: "0x10200000",366 stopApp: false,367 });368 } catch (err) {369 logger.warn(`Failed to launch settings app: ${err.message}`);370 if (throwError) {371 throw err;372 }373 }374};375helpers.pushUnlock = async function (adb) {376 logger.debug("Pushing unlock helper app to device...");377 await helpers.installHelperApp(adb, unlockApkPath, UNLOCK_HELPER_PKG_ID, 'Unlock');378};379/**380 * Extracts string.xml and converts it to string.json and pushes381 * it to /data/local/tmp/string.json on for use of bootstrap382 * If app is not present to extract string.xml it deletes remote strings.json383 * If app does not have strings.xml we push an empty json object to remote384 *385 * @param {?string} language - Language abbreviation, for example 'fr'. The default language386 * is used if this argument is not defined.387 * @param {Object} adb - The adb mofdule instance.388 * @param {Object} opts - Driver options dictionary.389 * @returns {Object} The dictionary, where string resourtces identifiers are keys390 * along with their corresponding values for the given language or an empty object391 * if no matching resources were extracted.392 */393helpers.pushStrings = async function (language, adb, opts) {394 const remoteDir = '/data/local/tmp';395 const stringsJson = 'strings.json';396 const remoteFile = `${remoteDir}/${stringsJson}`;397 // clean up remote string.json if present398 await adb.rimraf(remoteFile);399 if (_.isEmpty(opts.appPackage) || !(await fs.exists(opts.app))) {400 return {};401 }402 const stringsTmpDir = path.resolve(opts.tmpDir, opts.appPackage);403 try {404 logger.debug('Extracting strings from apk', opts.app, language, stringsTmpDir);405 const {apkStrings, localPath} = await adb.extractStringsFromApk(opts.app, language, stringsTmpDir);406 await adb.push(localPath, remoteDir);407 return apkStrings;408 } catch (err) {409 logger.warn(`Could not get strings, continuing anyway. Original error: ${err.message}`);410 await adb.shell('echo', [`'{}' > ${remoteFile}`]);411 } finally {412 await fs.rimraf(stringsTmpDir);413 }414 return {};415};416helpers.unlockWithUIAutomation = async function (driver, adb, unlockCapabilities) {417 let unlockType = unlockCapabilities.unlockType;418 if (!unlocker.isValidUnlockType(unlockType)) {419 throw new Error(`Invalid unlock type ${unlockType}`);420 }421 let unlockKey = unlockCapabilities.unlockKey;422 if (!unlocker.isValidKey(unlockType, unlockKey)) {423 throw new Error(`Missing unlockKey ${unlockKey} capability for unlockType ${unlockType}`);424 }425 const unlockMethod = {426 [PIN_UNLOCK]: unlocker.pinUnlock,427 [PASSWORD_UNLOCK]: unlocker.passwordUnlock,428 [PATTERN_UNLOCK]: unlocker.patternUnlock,429 [FINGERPRINT_UNLOCK]: unlocker.fingerprintUnlock430 }[unlockType];431 await unlockMethod(adb, driver, unlockCapabilities);432};433helpers.unlockWithHelperApp = async function (adb) {434 logger.info("Unlocking screen");435 try {436 await adb.forceStop(UNLOCK_HELPER_PKG_ID);437 } catch (e) {438 // Sometimes we can see the below error, but we can ignore it.439 // [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'440 logger.warn(`An error in unlockWithHelperApp: ${e.message}`);441 }442 let startOpts = {443 pkg: UNLOCK_HELPER_PKG_ID,444 activity: UNLOCK_HELPER_PKG_ACTIVITY,445 action: "android.intent.action.MAIN",446 category: "android.intent.category.LAUNCHER",447 flags: "0x10200000",448 stopApp: false,449 retry: false,450 waitDuration: 1000451 };452 // Unlock succeed with a couple of retries.453 let firstRun = true;454 await retry(3, async function () {455 // To reduce a time to call adb.isScreenLocked() since `adb shell dumpsys window` is easy to hang adb commands456 if (firstRun) {457 firstRun = false;458 } else {459 try {460 if (!(await adb.isScreenLocked())) {461 return;462 }463 } catch (e) {464 logger.warn(`Error in isScreenLocked: ${e.message}`);465 logger.warn("\"adb shell dumpsys window\" command has timed out.");466 logger.warn("The reason of this timeout is the delayed adb response. Resetting adb server can improve it.");467 }468 }469 logger.info(`Launching ${UNLOCK_HELPER_PKG_ID}`);470 // The command takes too much time so we should not call the command over twice continuously.471 await adb.startApp(startOpts);472 });473};474helpers.unlock = async function (driver, adb, capabilities) {475 if (!(await adb.isScreenLocked())) {476 logger.info("Screen already unlocked, doing nothing");477 return;478 }479 logger.debug("Screen is locked, trying to unlock");480 if (_.isUndefined(capabilities.unlockType)) {481 logger.warn("Using app unlock, this is going to be deprecated!");482 await helpers.unlockWithHelperApp(adb);483 } else {484 await helpers.unlockWithUIAutomation(driver, adb, {unlockType: capabilities.unlockType, unlockKey: capabilities.unlockKey});485 await helpers.verifyUnlock(adb);486 }487};488helpers.verifyUnlock = async function (adb) {489 await retryInterval(2, 1000, async () => {490 if (await adb.isScreenLocked()) {491 throw new Error("Screen did not unlock successfully, retrying");492 }493 logger.debug("Screen unlocked successfully");494 });495};496helpers.initDevice = async function (adb, opts) {497 await adb.waitForDevice();498 if (!opts.avd) {499 // pushSettingsApp required before calling ensureDeviceLocale for API Level 24+500 // await helpers.pushSettingsApp(adb);501 await helpers.setMockLocationApp(adb, SETTINGS_HELPER_PKG_ID);502 }503 await helpers.ensureDeviceLocale(adb, opts.language, opts.locale);504 await adb.startLogcat();505 let defaultIME;506 if (opts.unicodeKeyboard) {507 defaultIME = await helpers.initUnicodeKeyboard(adb);508 }509 if (_.isUndefined(opts.unlockType)) {510 //await helpers.pushUnlock(adb);511 }512 return defaultIME;513};514helpers.removeNullProperties = function (obj) {515 for (let key of _.keys(obj)) {516 if (_.isNull(obj[key]) || _.isUndefined(obj[key])) {517 delete obj[key];518 }519 }520};521helpers.truncateDecimals = function (number, digits) {522 let multiplier = Math.pow(10, digits),523 adjustedNum = number * multiplier,524 truncatedNum = Math[adjustedNum < 0 ? 'ceil' : 'floor'](adjustedNum);525 return truncatedNum / multiplier;526};527helpers.isChromeBrowser = function (browser) {528 return _.includes(Object.keys(CHROME_BROWSER_PACKAGE_ACTIVITY), (browser || '').toLowerCase());529};530helpers.getChromePkg = function (browser) {531 return CHROME_BROWSER_PACKAGE_ACTIVITY[browser.toLowerCase()] ||532 CHROME_BROWSER_PACKAGE_ACTIVITY.default;533};534helpers.removeAllSessionWebSocketHandlers = async function (server, sessionId) {535 if (!server || !_.isFunction(server.getWebSocketHandlers)) {536 return;537 }538 const activeHandlers = await server.getWebSocketHandlers(sessionId);539 for (const pathname of _.keys(activeHandlers)) {540 await server.removeWebSocketHandler(pathname);541 }542};543/**544 * Takes a desired capability and tries to JSON.parse it as an array,545 * and either returns the parsed array or a singleton array.546 *547 * @param {any} cap A desired capability548 */549helpers.parseArray = function (cap) {550 let parsedCaps;551 try {552 parsedCaps = JSON.parse(cap);553 } catch (ign) { }554 if (_.isArray(parsedCaps)) {555 return parsedCaps;556 } else if (_.isString(cap)) {557 return [cap];558 }559 throw new Error(`must provide a string or JSON Array; received ${cap}`);560};561helpers.bootstrap = Bootstrap;562helpers.unlocker = unlocker;...

Full Screen

Full Screen

driver-specs.js

Source:driver-specs.js Github

copy

Full Screen

1import chai from 'chai';2import chaiAsPromised from 'chai-as-promised';3import log from '../../lib/logger';4import sinon from 'sinon';5import helpers from '../../lib/android-helpers';6import { withMocks } from 'appium-test-support';7import AndroidDriver from '../..';8import ADB from 'appium-adb';9import { errors } from 'appium-base-driver';10import { fs } from 'appium-support';11import { SharedPrefsBuilder } from 'shared-preferences-builder';12let driver;13let sandbox = sinon.sandbox.create();14let expect = chai.expect;15chai.should();16chai.use(chaiAsPromised);17describe('driver', () => {18 describe('constructor', () => {19 it('should call BaseDriver constructor with opts', () => {20 let driver = new AndroidDriver({foo: 'bar'});21 driver.should.exist;22 driver.opts.foo.should.equal('bar');23 });24 it('should have this.findElOrEls', () => {25 let driver = new AndroidDriver({foo: 'bar'});26 driver.findElOrEls.should.exist;27 driver.findElOrEls.should.be.a('function');28 });29 });30 describe('emulator methods', () => {31 describe('fingerprint', () => {32 it('should be rejected if isEmulator is false', () => {33 let driver = new AndroidDriver();34 sandbox.stub(driver, 'isEmulator').returns(false);35 driver.fingerprint(1111).should.eventually.be.rejectedWith("fingerprint method is only available for emulators");36 driver.isEmulator.calledOnce.should.be.true;37 });38 });39 describe('sendSMS', () => {40 it('sendSMS should be rejected if isEmulator is false', () => {41 let driver = new AndroidDriver();42 sandbox.stub(driver, 'isEmulator').returns(false);43 driver.sendSMS(4509, "Hello Appium").should.eventually.be.rejectedWith("sendSMS method is only available for emulators");44 driver.isEmulator.calledOnce.should.be.true;45 });46 });47 });48 describe('sharedPreferences', () => {49 driver = new AndroidDriver();50 let adb = new ADB();51 driver.adb = adb;52 let builder = new SharedPrefsBuilder();53 describe('should skip setting sharedPreferences', withMocks({driver}, (mocks) => {54 it('on undefined name', async () => {55 driver.opts.sharedPreferences = {};56 (await driver.setSharedPreferences()).should.be.false;57 mocks.driver.verify();58 });59 }));60 describe('should set sharedPreferences', withMocks({driver, adb, builder, fs}, (mocks) => {61 it('on defined sharedPreferences object', async () => {62 driver.opts.appPackage = 'io.appium.test';63 driver.opts.sharedPreferences = {64 name: 'com.appium.prefs',65 prefs: [{type: 'string', name: 'mystr', value:'appium rocks!'}]66 };67 mocks.driver.expects('getPrefsBuilder').once().returns(builder);68 mocks.builder.expects('build').once();69 mocks.builder.expects('toFile').once();70 mocks.adb.expects('shell').once()71 .withExactArgs(['mkdir', '-p', '/data/data/io.appium.test/shared_prefs']);72 mocks.adb.expects('push').once()73 .withExactArgs('/tmp/com.appium.prefs.xml', '/data/data/io.appium.test/shared_prefs/com.appium.prefs.xml');74 mocks.fs.expects('exists').once()75 .withExactArgs('/tmp/com.appium.prefs.xml')76 .returns(true);77 mocks.fs.expects('unlink').once()78 .withExactArgs('/tmp/com.appium.prefs.xml');79 await driver.setSharedPreferences();80 mocks.driver.verify();81 mocks.adb.verify();82 mocks.builder.verify();83 mocks.fs.verify();84 });85 }));86 });87 describe('createSession', () => {88 beforeEach(() => {89 driver = new AndroidDriver();90 sandbox.stub(driver, 'checkAppPresent');91 sandbox.stub(driver, 'checkPackagePresent');92 sandbox.stub(driver, 'startAndroidSession');93 sandbox.stub(ADB, 'createADB', async (opts) => {94 return {95 getDevicesWithRetry: async () => {96 return [97 {udid: 'emulator-1234'},98 {udid: 'rotalume-1337'}99 ];100 },101 getPortFromEmulatorString: () => {102 return 1234;103 },104 setDeviceId: () => {},105 setEmulatorPort: () => {},106 adbPort: opts.adbPort,107 networkSpeed: () => {}108 };109 });110 });111 afterEach(() => {112 sandbox.restore();113 });114 it('should verify device is an emulator', async () => {115 driver.opts.avd = "Nexus_5X_Api_23";116 driver.isEmulator().should.equal(true);117 driver.opts.avd = undefined;118 driver.opts.udid = "emulator-5554";119 driver.isEmulator().should.equal(true);120 driver.opts.udid = "01234567889";121 driver.isEmulator().should.equal(false);122 });123 it('should get java version if none is provided', async () => {124 await driver.createSession({platformName: 'Android', deviceName: 'device', app: '/path/to/some.apk'});125 driver.opts.javaVersion.should.exist;126 });127 it('should get browser package details if browserName is provided', async () => {128 sandbox.spy(helpers, 'getChromePkg');129 await driver.createSession({platformName: 'Android', deviceName: 'device', browserName: 'Chrome'});130 helpers.getChromePkg.calledOnce.should.be.true;131 });132 it('should check an app is present', async () => {133 await driver.createSession({platformName: 'Android', deviceName: 'device', app: '/path/to/some.apk'});134 driver.checkAppPresent.calledOnce.should.be.true;135 });136 it('should check a package is present', async () => {137 await driver.createSession({platformName: 'Android', deviceName: 'device', appPackage: 'some.app.package'});138 driver.checkPackagePresent.calledOnce.should.be.true;139 });140 it('should accept a package via the app capability', async () => {141 await driver.createSession({platformName: 'Android', deviceName: 'device', app: 'some.app.package'});142 driver.checkPackagePresent.calledOnce.should.be.true;143 });144 it('should add server details to caps', async () => {145 await driver.createSession({platformName: 'Android', deviceName: 'device', appPackage: 'some.app.package'});146 driver.caps.webStorageEnabled.should.exist;147 });148 it('should delete a session on failure', async () => {149 // Force an error to make sure deleteSession gets called150 sandbox.stub(helpers, 'getJavaVersion').throws();151 sandbox.stub(driver, 'deleteSession');152 try {153 await driver.createSession({platformName: 'Android', deviceName: 'device', appPackage: 'some.app.package'});154 } catch (ign) {}155 driver.deleteSession.calledOnce.should.be.true;156 });157 it('should pass along adbPort capability to ADB', async () => {158 await driver.createSession({platformName: 'Android', deviceName: 'device', appPackage: 'some.app.package', adbPort: 1111});159 driver.adb.adbPort.should.equal(1111);160 });161 it('should proxy screenshot if nativeWebScreenshot is off', async () => {162 await driver.createSession({platformName: 'Android', deviceName: 'device', browserName: 'chrome', nativeWebScreenshot: false});163 driver.getProxyAvoidList().should.have.length(8);164 });165 it('should not proxy screenshot if nativeWebScreenshot is on', async () => {166 await driver.createSession({platformName: 'Android', deviceName: 'device', browserName: 'chrome', nativeWebScreenshot: true});167 driver.getProxyAvoidList().should.have.length(9);168 });169 it('should set networkSpeed before launching app', async () => {170 sandbox.stub(driver, 'isEmulator').returns(true);171 sandbox.stub(helpers, 'ensureNetworkSpeed').returns('full');172 await driver.createSession({platformName: 'Android', deviceName: 'device', appPackage: 'some.app.package', networkSpeed: 'edge'});173 driver.isEmulator.calledOnce.should.be.true;174 helpers.ensureNetworkSpeed.calledOnce.should.be.true;175 });176 });177 describe('deleteSession', () => {178 beforeEach(async () => {179 driver = new AndroidDriver();180 driver.adb = new ADB();181 driver.bootstrap = new helpers.bootstrap(driver.adb);182 sandbox.stub(driver, 'stopChromedriverProxies');183 sandbox.stub(driver.adb, 'setIME');184 sandbox.stub(driver.adb, 'forceStop');185 sandbox.stub(driver.adb, 'goToHome');186 sandbox.stub(driver.adb, 'uninstallApk');187 sandbox.stub(driver.adb, 'stopLogcat');188 sandbox.stub(driver.bootstrap, 'shutdown');189 sandbox.spy(log, 'debug');190 });191 afterEach(() => {192 sandbox.restore();193 });194 it('should not do anything if Android Driver has already shut down', async () => {195 driver.bootstrap = null;196 await driver.deleteSession();197 log.debug.callCount.should.eql(3);198 driver.stopChromedriverProxies.called.should.be.false;199 driver.adb.stopLogcat.called.should.be.true;200 });201 it('should reset keyboard to default IME', async () => {202 driver.opts.unicodeKeyboard = true;203 driver.opts.resetKeyboard = true;204 driver.defaultIME = 'someDefaultIME';205 await driver.deleteSession();206 driver.adb.setIME.calledOnce.should.be.true;207 });208 it('should force stop non-Chrome sessions', async () => {209 await driver.deleteSession();210 driver.adb.forceStop.calledOnce.should.be.true;211 });212 it('should uninstall APK if required', async () => {213 driver.opts.fullReset = true;214 await driver.deleteSession();215 driver.adb.uninstallApk.calledOnce.should.be.true;216 });217 });218 describe('dismissChromeWelcome', () => {219 before(async () => {220 driver = new AndroidDriver();221 });222 it('should verify chromeOptions args', () => {223 driver.opts = {};224 driver.shouldDismissChromeWelcome().should.be.false;225 driver.opts = {chromeOptions: {}};226 driver.shouldDismissChromeWelcome().should.be.false;227 driver.opts = {chromeOptions: {args: []}};228 driver.shouldDismissChromeWelcome().should.be.false;229 driver.opts = {chromeOptions: {args: "--no-first-run"}};230 driver.shouldDismissChromeWelcome().should.be.false;231 driver.opts = {chromeOptions: {args: ["--disable-dinosaur-easter-egg"]}};232 driver.shouldDismissChromeWelcome().should.be.false;233 driver.opts = {chromeOptions: {args: ["--no-first-run"]}};234 driver.shouldDismissChromeWelcome().should.be.true;235 });236 });237 describe('initAUT', withMocks({helpers}, (mocks) => {238 beforeEach(async () => {239 driver = new AndroidDriver();240 driver.caps = {};241 });242 it('should throw error if run with full reset', async () => {243 driver.opts = {appPackage: "app.package", appActivity: "act", fullReset: true};244 await driver.initAUT().should.be.rejectedWith(/Full reset requires an app capability/);245 });246 it('should reset if run with fast reset', async () => {247 driver.opts = {appPackage: "app.package", appActivity: "act", fullReset: false, fastReset: true};248 driver.adb = "mock_adb";249 mocks.helpers.expects("resetApp").withExactArgs("mock_adb", undefined, "app.package", true);250 await driver.initAUT();251 mocks.helpers.verify();252 });253 it('should keep data if run without reset', async () => {254 driver.opts = {appPackage: "app.package", appActivity: "act", fullReset: false, fastReset: false};255 mocks.helpers.expects("resetApp").never();256 await driver.initAUT();257 mocks.helpers.verify();258 });259 }));260 describe('startAndroidSession', () => {261 beforeEach(async () => {262 driver = new AndroidDriver();263 driver.adb = new ADB();264 driver.bootstrap = new helpers.bootstrap(driver.adb);265 driver.settings = { update () {} };266 driver.caps = {};267 // create a fake bootstrap because we can't mock268 // driver.bootstrap.<whatever> in advance269 let fakeBootstrap = {start () {},270 onUnexpectedShutdown: {catch () {}}271 };272 sandbox.stub(helpers, 'initDevice');273 sandbox.stub(helpers, 'unlock');274 sandbox.stub(helpers, 'bootstrap').returns(fakeBootstrap);275 sandbox.stub(driver, 'initAUT');276 sandbox.stub(driver, 'startAUT');277 sandbox.stub(driver, 'defaultWebviewName');278 sandbox.stub(driver, 'setContext');279 sandbox.stub(driver, 'startChromeSession');280 sandbox.stub(driver, 'dismissChromeWelcome');281 sandbox.stub(driver.settings, 'update');282 sandbox.stub(driver.adb, 'getPlatformVersion');283 sandbox.stub(driver.adb, 'getScreenSize');284 sandbox.stub(driver.adb, 'getModel');285 sandbox.stub(driver.adb, 'getManufacturer');286 });287 afterEach(() => {288 sandbox.restore();289 });290 it('should set actual platform version', async () => {291 await driver.startAndroidSession();292 driver.adb.getPlatformVersion.calledOnce.should.be.true;293 });294 it('should auto launch app if it is on the device', async () => {295 driver.opts.autoLaunch = true;296 await driver.startAndroidSession();297 driver.initAUT.calledOnce.should.be.true;298 });299 it('should handle chrome sessions', async () => {300 driver.opts.browserName = 'Chrome';301 await driver.startAndroidSession();302 driver.startChromeSession.calledOnce.should.be.true;303 });304 it('should unlock the device', async () => {305 await driver.startAndroidSession();306 helpers.unlock.calledOnce.should.be.true;307 });308 it('should start AUT if auto lauching', async () => {309 driver.opts.autoLaunch = true;310 await driver.startAndroidSession();311 driver.initAUT.calledOnce.should.be.true;312 });313 it('should not start AUT if not auto lauching', async () => {314 driver.opts.autoLaunch = false;315 await driver.startAndroidSession();316 driver.initAUT.calledOnce.should.be.false;317 });318 it('should set the context if autoWebview is requested', async () => {319 driver.opts.autoWebview = true;320 await driver.startAndroidSession();321 driver.defaultWebviewName.calledOnce.should.be.true;322 driver.setContext.calledOnce.should.be.true;323 });324 it('should set the context if autoWebview is requested using timeout', async () => {325 driver.setContext.onCall(0).throws(errors.NoSuchContextError);326 driver.setContext.onCall(1).returns();327 driver.opts.autoWebview = true;328 driver.opts.autoWebviewTimeout = 5000;329 await driver.startAndroidSession();330 driver.defaultWebviewName.calledOnce.should.be.true;331 driver.setContext.calledTwice.should.be.true;332 });333 it('should respect timeout if autoWebview is requested', async function () {334 this.timeout(10000);335 driver.setContext.throws(new errors.NoSuchContextError());336 let begin = Date.now();337 driver.opts.autoWebview = true;338 driver.opts.autoWebviewTimeout = 5000;339 await driver.startAndroidSession().should.eventually.be.rejected;340 driver.defaultWebviewName.calledOnce.should.be.true;341 // we have a timeout of 5000ms, retrying on 500ms, so expect 10 times342 driver.setContext.callCount.should.equal(10);343 let end = Date.now();344 (end - begin).should.be.above(5000);345 });346 it('should not set the context if autoWebview is not requested', async () => {347 await driver.startAndroidSession();348 driver.defaultWebviewName.calledOnce.should.be.false;349 driver.setContext.calledOnce.should.be.false;350 });351 it('should set ignoreUnimportantViews cap', async () => {352 driver.opts.ignoreUnimportantViews = true;353 await driver.startAndroidSession();354 driver.settings.update.calledOnce.should.be.true;355 driver.settings.update.firstCall.args[0].ignoreUnimportantViews.should.be.true;356 });357 it('should not call dismissChromeWelcome on missing chromeOptions', async () => {358 driver.opts.browserName = 'Chrome';359 await driver.startAndroidSession();360 driver.dismissChromeWelcome.calledOnce.should.be.false;361 });362 it('should call dismissChromeWelcome', async () => {363 driver.opts.browserName = 'Chrome';364 driver.opts.chromeOptions = {365 "args" : ["--no-first-run"]366 };367 await driver.startAndroidSession();368 driver.dismissChromeWelcome.calledOnce.should.be.true;369 });370 });371 describe('validateDesiredCaps', () => {372 before(() => {373 driver = new AndroidDriver();374 });375 it('should throw an error if caps do not contain an app, package or valid browser', () => {376 expect(() => {377 driver.validateDesiredCaps({platformName: 'Android', deviceName: 'device'});378 }).to.throw(/must include/);379 expect(() => {380 driver.validateDesiredCaps({platformName: 'Android', deviceName: 'device', browserName: 'Netscape Navigator'});381 }).to.throw(/must include/);382 });383 it('should not throw an error if caps contain an app, package or valid browser', () => {384 expect(() => {385 driver.validateDesiredCaps({platformName: 'Android', deviceName: 'device', app: '/path/to/some.apk'});386 }).to.not.throw(Error);387 expect(() => {388 driver.validateDesiredCaps({platformName: 'Android', deviceName: 'device', browserName: 'Chrome'});389 }).to.not.throw(Error);390 expect(() => {391 driver.validateDesiredCaps({platformName: 'Android', deviceName: 'device', appPackage: 'some.app.package'});392 }).to.not.throw(/must include/);393 });394 it('should not be sensitive to platform name casing', () => {395 expect(() => {396 driver.validateDesiredCaps({platformName: 'AnDrOiD', deviceName: 'device', app: '/path/to/some.apk'});397 }).to.not.throw(Error);398 });399 it('should not throw an error if caps contain both an app and browser, for grid compatibility', () => {400 expect(() => {401 driver.validateDesiredCaps({platformName: 'Android', deviceName: 'device', app: '/path/to/some.apk', browserName: 'iPhone'});402 }).to.not.throw(Error);403 });404 it('should not throw an error if caps contain androidScreenshotPath capability', () => {405 expect(() => {406 driver.validateDesiredCaps({platformName: 'Android', deviceName: 'device', app: '/path/to/some.apk', androidScreenshotPath: '/path/to/screenshotdir'});407 }).to.not.throw(Error);408 });409 });410 describe('proxying', () => {411 before(() => {412 driver = new AndroidDriver();413 driver.sessionId = 'abc';414 });415 describe('#proxyActive', () => {416 it('should exist', () => {417 driver.proxyActive.should.be.an.instanceof(Function);418 });419 it('should return false', () => {420 driver.proxyActive('abc').should.be.false;421 });422 it('should throw an error if session id is wrong', () => {423 (() => { driver.proxyActive('aaa'); }).should.throw;424 });425 });426 describe('#getProxyAvoidList', () => {427 it('should exist', () => {428 driver.getProxyAvoidList.should.be.an.instanceof(Function);429 });430 it('should return jwpProxyAvoid array', () => {431 let avoidList = driver.getProxyAvoidList('abc');432 avoidList.should.be.an.instanceof(Array);433 avoidList.should.eql(driver.jwpProxyAvoid);434 });435 it('should throw an error if session id is wrong', () => {436 (() => { driver.getProxyAvoidList('aaa'); }).should.throw;437 });438 });439 describe('#canProxy', () => {440 it('should exist', () => {441 driver.canProxy.should.be.an.instanceof(Function);442 });443 it('should return false', () => {444 driver.canProxy('abc').should.be.false;445 });446 it('should throw an error if session id is wrong', () => {447 (() => { driver.canProxy('aaa'); }).should.throw;448 });449 });450 });...

Full Screen

Full Screen

Using AI Code Generation

copy

Full Screen

1var wd = require('wd');2var asserters = wd.asserters;3var chai = require('chai');4var chaiAsPromised = require('chai-as-promised');5chai.use(chaiAsPromised);6chai.should();7chaiAsPromised.transferPromiseness = wd.transferPromiseness;8var AppiumAndroidDriver = require('appium-android-driver');9var helpers = new AppiumAndroidDriver.helpers();10var desiredCaps = {

Full Screen

Using AI Code Generation

copy

Full Screen

1var wd = require('wd');2var assert = require('assert');3var androidDriver = require('appium-android-driver');4var helpers = androidDriver.helpers;5wd.addPromiseChainMethod('initDevice', helpers.initDevice);6var desired = {7};8var driver = wd.promiseChainRemote('localhost', 4723);9 .init(desired)10 .initDevice()11 .then(function () {12 });

Full Screen

Using AI Code Generation

copy

Full Screen

1var wd = require('wd');2var chai = require('chai');3var chaiAsPromised = require('chai-as-promised');4var should = chai.should();5var Q = require('q');6var initDriver = require('./helpers/initDriver');7var initDevice = require('./helpers/initDevice');8chai.use(chaiAsPromised);9var driver = wd.promiseChainRemote('

Full Screen

Automation Testing Tutorials

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.

LambdaTest Learning Hubs:

YouTube

You could also refer to video tutorials over LambdaTest YouTube channel to get step by step demonstration from industry experts.

Run Appium Android Driver automation tests on LambdaTest cloud grid

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

Try LambdaTest Now !!

Get 100 minutes of automation test minutes FREE!!

Next-Gen App & Browser Testing Cloud

Was this article helpful?

Helpful

NotHelpful