From 31c32f9ad53cfb8222fbe2c2d985e307305a2245 Mon Sep 17 00:00:00 2001 From: Mykola Mokhnach Date: Thu, 11 Jan 2024 20:31:01 +0100 Subject: [PATCH] feat: Use APIs imported from the io.appium.settings package (#897) --- lib/commands/file-actions.js | 17 ++-- lib/commands/general.js | 9 +- lib/commands/media-projection.js | 123 +--------------------------- lib/commands/mixins.ts | 1 + lib/commands/network.js | 34 ++++---- lib/driver.ts | 10 +++ lib/helpers/android.ts | 81 +++++++++--------- lib/index.ts | 5 +- lib/stubs.ts | 7 -- package.json | 2 +- test/unit/android-helper-specs.js | 99 ---------------------- test/unit/commands/network-specs.js | 37 ++++----- 12 files changed, 105 insertions(+), 320 deletions(-) delete mode 100644 lib/stubs.ts diff --git a/lib/commands/file-actions.js b/lib/commands/file-actions.js index f7825763..4caf1a7e 100644 --- a/lib/commands/file-actions.js +++ b/lib/commands/file-actions.js @@ -37,18 +37,17 @@ function parseContainerPath(remotePath) { * and adds matching items to the device's media library. * Exceptions are ignored and written into the log. * - * @param {ADB} adb ADB instance + * @this {import('../driver').AndroidDriver} * @param {string} remotePath The file/folder path on the remote device - * @param {import('@appium/types').AppiumLogger} [log] Logger instance */ -async function scanMedia(adb, remotePath, log) { - log?.debug(`Performing media scan of '${remotePath}'`); +async function scanMedia(remotePath) { + this.log.debug(`Performing media scan of '${remotePath}'`); try { // https://github.com/appium/appium/issues/16184 - if ((await adb.getApiLevel()) >= 29) { - await adb.scanMedia(remotePath); + if ((await this.adb.getApiLevel()) >= 29) { + await this.settingsApp.scanMedia(remotePath); } else { - await adb.shell([ + await this.adb.shell([ 'am', 'broadcast', '-a', @@ -60,7 +59,7 @@ async function scanMedia(adb, remotePath, log) { } catch (e) { const err = /** @type {any} */ (e); // FIXME: what has a `stderr` prop? - log?.warn( + this.log.warn( `Ignoring an unexpected error upon media scanning of '${remotePath}': ${ err.stderr ?? err.message }` @@ -183,7 +182,7 @@ const FileActionsMixin = { // if we have pushed a file, it might be a media file, so ensure that // apps know about it - await scanMedia(this.adb, remotePath, this.log); + await scanMedia.bind(this)(remotePath); } } finally { if (await fs.exists(localFile)) { diff --git a/lib/commands/general.js b/lib/commands/general.js index ebfd81dd..a7430719 100644 --- a/lib/commands/general.js +++ b/lib/commands/general.js @@ -301,21 +301,20 @@ const GeneralMixin = { this.log.debug(`Parsed density value was NaN: "${out}"`); } // couldn't get anything, so error out - this.log.errorAndThrow('Failed to get display density property.'); - throw new Error(); // unreachable + throw this.log.errorAndThrow('Failed to get display density property.'); }, async mobilePerformEditorAction(opts) { const {action} = requireArgs('action', opts); - await this.adb.performEditorAction(action); + await this.settingsApp.performEditorAction(action); }, async mobileGetNotifications() { - return await this.adb.getNotifications(); + return await this.settingsApp.getNotifications(); }, async mobileListSms(opts) { - return await this.adb.getSmsList(opts); + return await this.settingsApp.getSmsList(opts); }, async mobileUnlock(opts = {}) { diff --git a/lib/commands/media-projection.js b/lib/commands/media-projection.js index 6eb54d8f..bdf0e72e 100644 --- a/lib/commands/media-projection.js +++ b/lib/commands/media-projection.js @@ -1,24 +1,12 @@ -// @ts-check - -import {fs, net, tempDir, util} from '@appium/support'; -import {waitForCondition} from 'asyncbox'; -import B from 'bluebird'; +import {fs, net, util} from '@appium/support'; import _ from 'lodash'; import moment from 'moment'; import path from 'node:path'; -import {SETTINGS_HELPER_PKG_ID} from '../helpers'; import {mixin} from './mixins'; // https://github.com/appium/io.appium.settings#internal-audio--video-recording const DEFAULT_EXT = '.mp4'; -const RECORDING_STARTUP_TIMEOUT_MS = 3 * 1000; -const RECORDING_STOP_TIMEOUT_MS = 3 * 1000; const MIN_API_LEVEL = 29; -const RECORDING_SERVICE_NAME = `${SETTINGS_HELPER_PKG_ID}/.recorder.RecorderService`; -const RECORDING_ACTIVITY_NAME = `${SETTINGS_HELPER_PKG_ID}/io.appium.settings.Settings`; -const RECORDING_ACTION_START = `${SETTINGS_HELPER_PKG_ID}.recording.ACTION_START`; -const RECORDING_ACTION_STOP = `${SETTINGS_HELPER_PKG_ID}.recording.ACTION_STOP`; -const RECORDINGS_ROOT = `/storage/emulated/0/Android/data/${SETTINGS_HELPER_PKG_ID}/files`; const DEFAULT_FILENAME_FORMAT = 'YYYY-MM-DDTHH-mm-ss'; /** @@ -82,109 +70,6 @@ async function verifyMediaProjectionRecordingIsSupported(adb) { } } -class MediaProjectionRecorder { - /** - * @param {ADB} adb - */ - constructor(adb) { - this.adb = adb; - } - - async isRunning() { - const stdout = await this.adb.shell([ - 'dumpsys', - 'activity', - 'services', - RECORDING_SERVICE_NAME, - ]); - return stdout.includes(RECORDING_SERVICE_NAME); - } - - /** - * - * @param {import('./types').StartMediaProjectionRecordingOpts} opts - * @returns {Promise} - */ - async start(opts = {}) { - if (await this.isRunning()) { - return false; - } - - await this.cleanup(); - const {filename, maxDurationSec, priority, resolution} = opts; - const args = ['am', 'start', '-n', RECORDING_ACTIVITY_NAME, '-a', RECORDING_ACTION_START]; - if (filename) { - args.push('--es', 'filename', filename); - } - if (maxDurationSec) { - args.push('--es', 'max_duration_sec', `${maxDurationSec}`); - } - if (priority) { - args.push('--es', 'priority', priority); - } - if (resolution) { - args.push('--es', 'resolution', resolution); - } - await this.adb.shell(args); - await new B((resolve, reject) => { - setTimeout(async () => { - if (!(await this.isRunning())) { - return reject( - new Error( - `The media projection recording is not running after ${RECORDING_STARTUP_TIMEOUT_MS}ms. ` + - `Please check the logcat output for more details.` - ) - ); - } - resolve(); - }, RECORDING_STARTUP_TIMEOUT_MS); - }); - return true; - } - - async cleanup() { - await this.adb.shell([`rm -f ${RECORDINGS_ROOT}/*`]); - } - - async pullRecent() { - const recordings = await this.adb.ls(RECORDINGS_ROOT, ['-tr']); - if (_.isEmpty(recordings)) { - return null; - } - - const dstPath = path.join(await tempDir.openDir(), recordings[0]); - // increase timeout to 5 minutes because it might take a while to pull a large video file - await this.adb.pull(`${RECORDINGS_ROOT}/${recordings[0]}`, dstPath, {timeout: 300000}); - return dstPath; - } - - async stop() { - if (!(await this.isRunning())) { - return false; - } - - await this.adb.shell([ - 'am', - 'start', - '-n', - RECORDING_ACTIVITY_NAME, - '-a', - RECORDING_ACTION_STOP, - ]); - try { - await waitForCondition(async () => !(await this.isRunning()), { - waitMs: RECORDING_STOP_TIMEOUT_MS, - intervalMs: 500, - }); - } catch (e) { - throw new Error( - `The attempt to stop the current media projection recording timed out after ` + - `${RECORDING_STOP_TIMEOUT_MS}ms` - ); - } - return true; - } -} /** * @type {import('./mixins').MediaProjectionMixin & ThisType} * @satisfies {import('@appium/types').ExternalDriver} @@ -194,7 +79,7 @@ const MediaProjectionMixin = { await verifyMediaProjectionRecordingIsSupported(this.adb); const {resolution, priority, maxDurationSec, filename} = options; - const recorder = new MediaProjectionRecorder(this.adb); + const recorder = this.settingsApp.makeMediaProjectionRecorder(); const fname = adjustMediaExtension(filename || moment().format(DEFAULT_FILENAME_FORMAT)); const didStart = await recorder.start({ resolution, @@ -215,14 +100,14 @@ const MediaProjectionMixin = { async mobileIsMediaProjectionRecordingRunning() { await verifyMediaProjectionRecordingIsSupported(this.adb); - const recorder = new MediaProjectionRecorder(this.adb); + const recorder = this.settingsApp.makeMediaProjectionRecorder(); return await recorder.isRunning(); }, async mobileStopMediaProjectionRecording(options = {}) { await verifyMediaProjectionRecordingIsSupported(this.adb); - const recorder = new MediaProjectionRecorder(this.adb); + const recorder = this.settingsApp.makeMediaProjectionRecorder(); if (await recorder.stop()) { this.log.info( 'Successfully stopped a media projection recording. Pulling the recorded media' diff --git a/lib/commands/mixins.ts b/lib/commands/mixins.ts index 1f447ba0..4361d3bb 100644 --- a/lib/commands/mixins.ts +++ b/lib/commands/mixins.ts @@ -711,6 +711,7 @@ export interface NetworkMixin { * decoupling to override behaviour in other drivers like UiAutomator2. */ setWifiState(state: boolean): Promise; + setDataState(state: boolean): Promise; toggleData(): Promise; toggleWiFi(): Promise; toggleFlightMode(): Promise; diff --git a/lib/commands/network.js b/lib/commands/network.js index 35c37e35..d096e9a1 100644 --- a/lib/commands/network.js +++ b/lib/commands/network.js @@ -1,5 +1,3 @@ -// @ts-check - import {mixin} from './mixins'; import _ from 'lodash'; import {errors} from 'appium/driver'; @@ -67,10 +65,10 @@ const NetworkMixin = { /** @type {(Promise|(() => Promise))[]} */ const setters = []; if (!_.isUndefined(wifi) && currentState.wifi !== Boolean(wifi)) { - setters.push(this.adb.setWifiState(wifi, this.isEmulator())); + setters.push(this.setWifiState(wifi)); } if (!_.isUndefined(data) && currentState.data !== Boolean(data)) { - setters.push(this.adb.setDataState(data, this.isEmulator())); + setters.push(this.setDataState(data)); } if (!_.isUndefined(airplaneMode) && currentState.airplaneMode !== Boolean(airplaneMode)) { setters.push(async () => { @@ -170,26 +168,30 @@ const NetworkMixin = { `${shouldEnableDataConnection ? 'enabled' : 'disabled'}` ); } else { - await this.adb.setDataState(shouldEnableDataConnection, this.isEmulator()); + await this.setDataState(shouldEnableDataConnection); } return await this.getNetworkConnection(); }, - async setWifiState(wifi) { - await this.adb.setWifiState(wifi, this.isEmulator()); + async setWifiState(isOn) { + await this.settingsApp.setWifiState(isOn, this.isEmulator()); + }, + + async setDataState(isOn) { + await this.settingsApp.setDataState(isOn, this.isEmulator()); }, async toggleData() { - let data = !(await this.adb.isDataOn()); - this.log.info(`Turning network data ${data ? 'on' : 'off'}`); - await this.adb.setWifiAndData({data}, this.isEmulator()); + const isOn = await this.adb.isDataOn(); + this.log.info(`Turning network data ${!isOn ? 'on' : 'off'}`); + await this.setDataState(!isOn); }, async toggleWiFi() { - let wifi = !(await this.adb.isWifiOn()); - this.log.info(`Turning WiFi ${wifi ? 'on' : 'off'}`); - await this.adb.setWifiAndData({wifi}, this.isEmulator()); + const isOn = await this.adb.isWifiOn(); + this.log.info(`Turning WiFi ${!isOn ? 'on' : 'off'}`); + await this.setWifiState(!isOn); }, async toggleFlightMode() { @@ -206,7 +208,7 @@ const NetworkMixin = { }, async setGeoLocation(location) { - await this.adb.setGeoLocation(location, this.isEmulator()); + await this.settingsApp.setGeoLocation(location, this.isEmulator()); try { return await this.getGeoLocation(); } catch (e) { @@ -224,11 +226,11 @@ const NetworkMixin = { async mobileRefreshGpsCache(opts = {}) { const {timeoutMs} = opts; - await this.adb.refreshGeoLocationCache(timeoutMs); + await this.settingsApp.refreshGeoLocationCache(timeoutMs); }, async getGeoLocation() { - const {latitude, longitude, altitude} = await this.adb.getGeoLocation(); + const {latitude, longitude, altitude} = await this.settingsApp.getGeoLocation(); return { latitude: parseFloat(String(latitude)) || GEO_EPSILON, longitude: parseFloat(String(longitude)) || GEO_EPSILON, diff --git a/lib/driver.ts b/lib/driver.ts index b03be96a..72d224ec 100644 --- a/lib/driver.ts +++ b/lib/driver.ts @@ -16,6 +16,7 @@ import {BaseDriver} from 'appium/driver'; import ANDROID_DRIVER_CONSTRAINTS, {AndroidDriverConstraints} from './constraints'; import {helpers} from './helpers'; import {newMethodMap} from './method-map'; +import { SettingsApp } from 'io.appium.settings'; export type AndroidDriverCaps = DriverCaps; export type W3CAndroidDriverCaps = W3CDriverCaps; @@ -31,6 +32,8 @@ class AndroidDriver adb: ADB; + _settingsApp: SettingsApp; + unlocker: typeof helpers.unlocker; apkStrings: StringRecord>; @@ -75,6 +78,13 @@ class AndroidDriver this.opts = opts as AndroidDriverOpts; } + get settingsApp() { + if (!this._settingsApp) { + this._settingsApp = new SettingsApp({adb: this.adb}); + } + return this._settingsApp; + } + isEmulator() { return helpers.isEmulator(this.adb, this.opts); } diff --git a/lib/helpers/android.ts b/lib/helpers/android.ts index 28572390..1676bec1 100644 --- a/lib/helpers/android.ts +++ b/lib/helpers/android.ts @@ -4,7 +4,13 @@ import type {AppiumServer, StringRecord} from '@appium/types'; import {ADB} from 'appium-adb'; import {retryInterval, waitForCondition} from 'asyncbox'; import B from 'bluebird'; -import {path as settingsApkPath} from 'io.appium.settings'; +import { + path as SETTINGS_APK_PATH, + SettingsApp, + SETTINGS_HELPER_ID, + UNICODE_IME, + EMPTY_IME, +} from 'io.appium.settings'; import _ from 'lodash'; import {EOL} from 'node:os'; import path from 'node:path'; @@ -57,8 +63,6 @@ const CHROME_BROWSER_PACKAGE_ACTIVITY = { activity: 'com.google.android.apps.chrome.Main', }, } as const; -const SETTINGS_HELPER_PKG_ID = 'io.appium.settings'; -const SETTING_NOTIFICATIONS_LISTENER_SERVICE = `${SETTINGS_HELPER_PKG_ID}/.NLService`; const EMULATOR_PATTERN = /\bemulator\b/i; // These constants are in sync with // https://developer.apple.com/documentation/xctest/xcuiapplicationstate/xcuiapplicationstaterunningbackground?language=objc @@ -68,7 +72,6 @@ const APP_STATE = { RUNNING_IN_BACKGROUND: 3, RUNNING_IN_FOREGROUND: 4, } as const; -const EMPTY_IME = `${SETTINGS_HELPER_PKG_ID}/.EmptyIME`; function ensureNetworkSpeed(adb: ADB, networkSpeed: string) { if (networkSpeed.toUpperCase() in adb.NETWORK_SPEED) { @@ -344,13 +347,8 @@ const AndroidHelpers: AndroidHelpers = { }, async ensureDeviceLocale(adb, language, country, script) { - if (!_.isString(language) && !_.isString(country)) { - logger.warn(`setDeviceLanguageCountry requires language or country.`); - logger.warn(`Got language: '${language}' and country: '${country}'`); - return; - } - - await adb.setDeviceLanguageCountry(language, country, script); + const settingsApp = new SettingsApp({adb}); + await settingsApp.setDeviceLocale(language!, country!, script); if (!(await adb.ensureCurrentLocale(language, country, script))) { const message = script @@ -686,10 +684,9 @@ const AndroidHelpers: AndroidHelpers = { const defaultIME = await adb.defaultIME(); logger.debug(`Unsetting previous IME ${defaultIME}`); - const appiumIME = `${SETTINGS_HELPER_PKG_ID}/.UnicodeIME`; - logger.debug(`Setting IME to '${appiumIME}'`); - await adb.enableIME(appiumIME); - await adb.setIME(appiumIME); + logger.debug(`Setting IME to '${UNICODE_IME}'`); + await adb.enableIME(UNICODE_IME); + await adb.setIME(UNICODE_IME); return defaultIME; }, @@ -755,7 +752,7 @@ const AndroidHelpers: AndroidHelpers = { await adb.shell([ 'appops', 'set', - resultPkgs[0] ?? SETTINGS_HELPER_PKG_ID, + resultPkgs[0] ?? SETTINGS_HELPER_ID, 'android:mock_location', 'deny', ]); @@ -793,65 +790,61 @@ const AndroidHelpers: AndroidHelpers = { logger.debug('Pushing settings apk to device...'); try { - await AndroidHelpers.installHelperApp(adb, settingsApkPath, SETTINGS_HELPER_PKG_ID); + await AndroidHelpers.installHelperApp(adb, SETTINGS_APK_PATH, SETTINGS_HELPER_ID); } catch (err) { if (throwError) { throw err; } logger.warn( - `Ignored error while installing '${settingsApkPath}': ` + + `Ignored error while installing '${SETTINGS_APK_PATH}': ` + `'${(err as Error).message}'. Features that rely on this helper ` + 'require the apk such as toggle WiFi and getting location ' + 'will raise an error if you try to use them.' ); } + const settingsApp = new SettingsApp({adb}); // Reinstall would stop the settings helper process anyway, so // there is no need to continue if the application is still running - if (await adb.isSettingsAppServiceRunningInForeground()) { + if (await settingsApp.isRunningInForeground()) { logger.debug( - `${SETTINGS_HELPER_PKG_ID} is already running. ` + + `${SETTINGS_HELPER_ID} is already running. ` + `There is no need to reset its permissions.` ); return; } - const apiLevel = await adb.getApiLevel(); - if (apiLevel >= 29) { - // https://github.com/appium/io.appium.settings#internal-audio--video-recording - try { - await adb.shell(['appops', 'set', SETTINGS_HELPER_PKG_ID, 'PROJECT_MEDIA', 'allow']); - } catch (err) { - logger.debug((err as Error).message); - } - try { - await adb.shell([ - 'cmd', - 'notification', - 'allow_listener', - SETTING_NOTIFICATIONS_LISTENER_SERVICE, - ]); - } catch (err) { - logger.debug((err as Error).message); + const fixSettingsAppPermissionsForLegacyApis = async () => { + if (await adb.getApiLevel() > 23) { + return; } - } - if (apiLevel <= 23) { + // Android 6- devices should have granted permissions // https://github.com/appium/appium/pull/11640#issuecomment-438260477 const perms = ['SET_ANIMATION_SCALE', 'CHANGE_CONFIGURATION', 'ACCESS_FINE_LOCATION']; - logger.info(`Granting permissions ${perms} to '${SETTINGS_HELPER_PKG_ID}'`); + logger.info(`Granting permissions ${perms} to '${SETTINGS_HELPER_ID}'`); await adb.grantPermissions( - SETTINGS_HELPER_PKG_ID, + SETTINGS_HELPER_ID, perms.map((x) => `android.permission.${x}`) ); + }; + + try { + await B.all([ + settingsApp.adjustNotificationsPermissions(), + settingsApp.adjustMediaProjectionServicePermissions(), + fixSettingsAppPermissionsForLegacyApis(), + ]); + } catch (e) { + logger.debug(e.stack); } // launch io.appium.settings app due to settings failing to be set // if the app is not launched prior to start the session on android 7+ // see https://github.com/appium/appium/issues/8957 try { - await adb.requireRunningSettingsApp({ + await settingsApp.requireRunning({ timeout: AndroidHelpers.isEmulator(adb, opts) ? 30000 : 5000, }); } catch (err) { @@ -1003,7 +996,7 @@ const AndroidHelpers: AndroidHelpers = { if (!AndroidHelpers.isEmulator(adb, opts)) { if (mockLocationApp || _.isUndefined(mockLocationApp)) { - await AndroidHelpers.setMockLocationApp(adb, mockLocationApp || SETTINGS_HELPER_PKG_ID); + await AndroidHelpers.setMockLocationApp(adb, mockLocationApp || SETTINGS_HELPER_ID); } else { await AndroidHelpers.resetMockLocation(adb); } @@ -1156,5 +1149,5 @@ const AndroidHelpers: AndroidHelpers = { }; export const helpers = AndroidHelpers; -export {APP_STATE, SETTINGS_HELPER_PKG_ID, ensureNetworkSpeed, prepareAvdArgs}; +export {APP_STATE, SETTINGS_HELPER_ID, ensureNetworkSpeed, prepareAvdArgs}; export default AndroidHelpers; diff --git a/lib/index.ts b/lib/index.ts index cbf627cc..3e38ac54 100644 --- a/lib/index.ts +++ b/lib/index.ts @@ -6,7 +6,10 @@ export type * from './commands'; export {ANDROID_DRIVER_CONSTRAINTS as commonCapConstraints} from './constraints'; export * from './driver'; export * as doctor from './doctor/checks'; -export {SETTINGS_HELPER_PKG_ID, default as androidHelpers} from './helpers/android'; +export { + SETTINGS_HELPER_ID as SETTINGS_HELPER_PKG_ID, + default as androidHelpers +} from './helpers/android'; export type * from './helpers/types'; export { CHROMIUM_WIN, diff --git a/lib/stubs.ts b/lib/stubs.ts deleted file mode 100644 index 30d83e21..00000000 --- a/lib/stubs.ts +++ /dev/null @@ -1,7 +0,0 @@ -/** - * This module contains delcaration stubs for dependencies that are untyped. - * @module - */ - -// @ts-ignore It is expected this module is untyped -declare module 'io.appium.settings'; diff --git a/package.json b/package.json index 6092aee2..5e74d7f7 100644 --- a/package.json +++ b/package.json @@ -62,7 +62,7 @@ "asyncbox": "^3.0.0", "axios": "^1.x", "bluebird": "^3.4.7", - "io.appium.settings": "^5.3.0", + "io.appium.settings": "^5.7.1", "lodash": "^4.17.4", "lru-cache": "^10.0.1", "moment": "^2.24.0", diff --git a/test/unit/android-helper-specs.js b/test/unit/android-helper-specs.js index 00f79b8f..bfa9fdda 100644 --- a/test/unit/android-helper-specs.js +++ b/test/unit/android-helper-specs.js @@ -155,59 +155,6 @@ describe('Android Helpers', function () { ensureNetworkSpeed(adb, 'invalid').should.be.equal('full'); }); }); - describe( - 'ensureDeviceLocale', - withMocks({adb}, (mocks) => { - it('should call setDeviceLanguageCountry', async function () { - mocks.adb.expects('setDeviceLanguageCountry').withExactArgs('en', 'US', undefined).once(); - mocks.adb - .expects('ensureCurrentLocale') - .withExactArgs('en', 'US', undefined) - .once() - .returns(true); - await helpers.ensureDeviceLocale(adb, 'en', 'US', undefined); - mocks.adb.verify(); - }); - it('should call setDeviceLanguageCountry without script', async function () { - mocks.adb.expects('setDeviceLanguageCountry').withExactArgs('en', 'US', undefined).once(); - mocks.adb - .expects('ensureCurrentLocale') - .withExactArgs('en', 'US', undefined) - .once() - .returns(true); - await helpers.ensureDeviceLocale(adb, 'en', 'US', undefined); - mocks.adb.verify(); - }); - it('should call setDeviceLanguageCountry with script', async function () { - mocks.adb.expects('setDeviceLanguageCountry').withExactArgs('zh', 'CN', 'Hans').once(); - mocks.adb - .expects('ensureCurrentLocale') - .withExactArgs('zh', 'CN', 'Hans') - .once() - .returns(true); - await helpers.ensureDeviceLocale(adb, 'zh', 'CN', 'Hans'); - mocks.adb.verify(); - }); - it('should never call setDeviceLanguageCountry', async function () { - mocks.adb.expects('setDeviceLanguageCountry').never(); - mocks.adb.expects('getApiLevel').never(); - await helpers.ensureDeviceLocale(adb); - mocks.adb.verify(); - }); - it('should call setDeviceLanguageCountry with throw', async function () { - mocks.adb.expects('setDeviceLanguageCountry').withExactArgs('fr', 'FR', undefined).once(); - mocks.adb - .expects('ensureCurrentLocale') - .withExactArgs('fr', 'FR', undefined) - .once() - .returns(false); - await helpers - .ensureDeviceLocale(adb, 'fr', 'FR') - .should.be.rejectedWith(Error, `Failed to set language: fr and country: FR`); - mocks.adb.verify(); - }); - }) - ); describe('getDeviceInfoFromCaps', function () { // list of device/emu udids to their os versions @@ -630,52 +577,6 @@ describe('Android Helpers', function () { }); }) ); - describe( - 'pushSettingsApp', - withMocks({adb}, (mocks) => { - it('should skip granting permissions if the app is already running on over API level 23+ devices', async function () { - mocks.adb.expects('installOrUpgrade').once().returns(true); - mocks.adb.expects('isSettingsAppServiceRunningInForeground').once().returns(true); - mocks.adb.expects('getApiLevel').never(); - mocks.adb.expects('grantPermissions').never(); - await helpers.pushSettingsApp(adb); - mocks.adb.verify(); - }); - it('should not skip granting permissions if the app is already running on under API level 22 devices', async function () { - mocks.adb.expects('installOrUpgrade').once().returns(true); - mocks.adb.expects('isSettingsAppServiceRunningInForeground').once().returns(true); - mocks.adb.expects('getApiLevel').never(); - mocks.adb.expects('grantPermissions').never(); - await helpers.pushSettingsApp(adb); - mocks.adb.verify(); - }); - it('should launch settings app if it isnt running on over API level 24 devices', async function () { - mocks.adb.expects('installOrUpgrade').once().returns(true); - mocks.adb.expects('isSettingsAppServiceRunningInForeground').once().returns(false); - mocks.adb.expects('getApiLevel').once().returns(24); - mocks.adb.expects('requireRunningSettingsApp').once(); - await helpers.pushSettingsApp(adb); - mocks.adb.verify(); - }); - it('should launch settings app if it isnt running on under API level 23 devices', async function () { - mocks.adb.expects('installOrUpgrade').once().returns(true); - mocks.adb.expects('isSettingsAppServiceRunningInForeground').once().returns(false); - mocks.adb.expects('getApiLevel').once().returns(23); - mocks.adb - .expects('grantPermissions') - .once() - .withExactArgs('io.appium.settings', [ - 'android.permission.SET_ANIMATION_SCALE', - 'android.permission.CHANGE_CONFIGURATION', - 'android.permission.ACCESS_FINE_LOCATION', - ]) - .returns(true); - mocks.adb.expects('requireRunningSettingsApp').once(); - await helpers.pushSettingsApp(adb); - mocks.adb.verify(); - }); - }) - ); describe( 'setMockLocationApp', withMocks({adb}, (mocks) => { diff --git a/test/unit/commands/network-specs.js b/test/unit/commands/network-specs.js index ed02639a..71e585cc 100644 --- a/test/unit/commands/network-specs.js +++ b/test/unit/commands/network-specs.js @@ -4,9 +4,11 @@ import sinon from 'sinon'; import ADB from 'appium-adb'; import {AndroidDriver} from '../../../lib/driver'; import B from 'bluebird'; +import { SettingsApp } from 'io.appium.settings'; let driver; let adb; +let settingsApp; let sandbox = sinon.createSandbox(); chai.should(); chai.use(chaiAsPromised); @@ -17,6 +19,9 @@ describe('Network', function () { adb = new ADB(); driver.adb = adb; sandbox.stub(adb); + settingsApp = new SettingsApp({adb}); + driver._settingsApp = settingsApp; + sandbox.stub(settingsApp); sandbox.stub(driver, 'isEmulator'); sandbox.stub(B, 'delay'); }); @@ -59,6 +64,7 @@ describe('Network', function () { describe('setNetworkConnection', function () { beforeEach(function () { sandbox.stub(driver, 'setWifiState'); + sandbox.stub(driver, 'setDataState'); driver.isEmulator.returns(false); }); it('should turn off wifi and data', async function () { @@ -67,7 +73,7 @@ describe('Network', function () { adb.setAirplaneMode.called.should.be.false; adb.broadcastAirplaneMode.called.should.be.false; driver.setWifiState.calledWithExactly(false).should.be.true; - adb.setDataState.calledWithExactly(false, false).should.be.true; + driver.setDataState.calledWithExactly(false).should.be.true; }); it('should turn on and broadcast airplane mode', async function () { sandbox.stub(driver, 'getNetworkConnection').returns(0); @@ -76,7 +82,7 @@ describe('Network', function () { adb.setAirplaneMode.calledWithExactly(true).should.be.true; adb.broadcastAirplaneMode.calledWithExactly(true).should.be.true; driver.setWifiState.called.should.be.false; - adb.setDataState.called.should.be.false; + driver.setDataState.called.should.be.false; }); it('should turn on wifi', async function () { sandbox.stub(driver, 'getNetworkConnection').returns(0); @@ -84,7 +90,7 @@ describe('Network', function () { adb.setAirplaneMode.called.should.be.false; adb.broadcastAirplaneMode.called.should.be.false; driver.setWifiState.calledWithExactly(true).should.be.true; - adb.setDataState.called.should.be.false; + driver.setDataState.called.should.be.false; }); it('should turn on data', async function () { sandbox.stub(driver, 'getNetworkConnection').returns(0); @@ -92,7 +98,7 @@ describe('Network', function () { adb.setAirplaneMode.called.should.be.false; adb.broadcastAirplaneMode.called.should.be.false; driver.setWifiState.called.should.be.false; - adb.setDataState.calledWithExactly(true, false).should.be.true; + driver.setDataState.calledWithExactly(true).should.be.true; }); it('should turn on data and wifi', async function () { sandbox.stub(driver, 'getNetworkConnection').returns(0); @@ -100,32 +106,25 @@ describe('Network', function () { adb.setAirplaneMode.called.should.be.false; adb.broadcastAirplaneMode.called.should.be.false; driver.setWifiState.calledWithExactly(true).should.be.true; - adb.setDataState.calledWithExactly(true, false).should.be.true; - }); - }); - describe('setWifiState', function () { - it('should set wifi state', async function () { - driver.isEmulator.returns('is_emu'); - await driver.setWifiState('wifi_state'); - adb.setWifiState.calledWithExactly('wifi_state', 'is_emu').should.be.true; + driver.setDataState.calledWithExactly(true).should.be.true; }); }); describe('toggleData', function () { it('should toggle data', async function () { adb.isDataOn.returns(false); driver.isEmulator.returns('is_emu'); - adb.setWifiAndData.returns(''); + settingsApp.setDataState.returns(''); await driver.toggleData(); - adb.setWifiAndData.calledWithExactly({data: true}, 'is_emu').should.be.true; + settingsApp.setDataState.calledWithExactly(true, 'is_emu').should.be.true; }); }); describe('toggleWiFi', function () { it('should toggle wifi', async function () { adb.isWifiOn.returns(false); driver.isEmulator.returns('is_emu'); - adb.setWifiAndData.returns(''); + settingsApp.setWifiState.returns(''); await driver.toggleWiFi(); - adb.setWifiAndData.calledWithExactly({wifi: true}, 'is_emu').should.be.true; + settingsApp.setWifiState.calledWithExactly(true, 'is_emu').should.be.true; }); }); describe('toggleFlightMode', function () { @@ -148,8 +147,8 @@ describe('Network', function () { }); describe('setGeoLocation', function () { it('should return location in use after setting', async function () { - adb.setGeoLocation.withArgs('location', 'is_emu').returns('res'); - adb.getGeoLocation.returns({ + settingsApp.setGeoLocation.withArgs('location', 'is_emu').returns('res'); + settingsApp.getGeoLocation.returns({ latitude: '1.1', longitude: '2.2', altitude: '3.3', @@ -163,7 +162,7 @@ describe('Network', function () { }); describe('getGeoLocation', function () { it('should get location', async function () { - adb.getGeoLocation.returns({ + settingsApp.getGeoLocation.returns({ latitude: '1.1', longitude: '2.2', });