From 25af259311b5957b4a42275d890605febcab85ea Mon Sep 17 00:00:00 2001 From: Kazuaki Matsuo Date: Sat, 25 Nov 2023 00:53:55 -0800 Subject: [PATCH 01/15] feat: use xcrun privacy instead of wix --- lib/extensions/permissions.js | 89 ++++++++++++++++++----------------- lib/simulator-xcode-9.js | 42 +++++++++++++++-- 2 files changed, 82 insertions(+), 49 deletions(-) diff --git a/lib/extensions/permissions.js b/lib/extensions/permissions.js index 562ebf9..134a6b2 100644 --- a/lib/extensions/permissions.js +++ b/lib/extensions/permissions.js @@ -1,6 +1,5 @@ import log from '../logger'; import _ from 'lodash'; -import { fs, util } from '@appium/support'; import { exec } from 'teen_process'; import path from 'path'; @@ -11,7 +10,7 @@ const STATUS = Object.freeze({ LIMITED: 'limited', }); -const WIX_SIM_UTILS = 'applesimutils'; + const SERVICES = Object.freeze({ calendar: 'kTCCServiceCalendar', camera: 'kTCCServiceCamera', @@ -36,10 +35,6 @@ function toInternalServiceName (serviceName) { ); } -function formatStatus (status) { - return [STATUS.UNSET, STATUS.NO].includes(status) ? _.toUpper(status) : status; -} - /** * Runs a command line sqlite3 query * @@ -58,46 +53,54 @@ async function execSQLiteQuery (db, query) { } } -async function execWix (args) { - try { - await fs.which(WIX_SIM_UTILS); - } catch (e) { - throw new Error( - `${WIX_SIM_UTILS} binary has not been found in your PATH. ` + - `Please install it ('brew tap wix/brew && brew install wix/brew/applesimutils') to ` + - `be able to change application permissions` - ); - } - - log.debug(`Executing: ${WIX_SIM_UTILS} ${util.quote(args)}`); - try { - const {stdout} = await exec(WIX_SIM_UTILS, args); - log.debug(`Command output: ${stdout}`); - return stdout; - } catch (e) { - throw new Error(`Cannot execute "${WIX_SIM_UTILS} ${util.quote(args)}". Original error: ${e.stderr || e.message}`); - } -} - /** * Sets permissions for the given application * - * @param {string} udid - udid of the target simulator device. + * @param {Object} simctl - simctl object * @param {string} bundleId - bundle identifier of the target application. - * @param {Object} permissionsMapping - An object, where keys ar service names - * and values are corresponding state values. See https://github.com/wix/AppleSimulatorUtils + * @param {Object} permissionsMapping - An object, where keys are service names + * and values are corresponding state values. See the result of `xcrun simctl privacy` * for more details on available service names and statuses. * @throws {Error} If there was an error while changing permissions. */ -async function setAccess (udid, bundleId, permissionsMapping) { - const permissionsArg = _.toPairs(permissionsMapping) - .map((x) => `${x[0]}=${formatStatus(x[1])}`) - .join(','); - return await execWix([ - '--byId', udid, - '--bundle', bundleId, - '--setPermissions', permissionsArg, - ]); +async function setAccess (simctl, bundleId, permissionsMapping) { + const grantPermissions = []; + const revokePermissions = []; + const resetPermissions = []; + + for (const serviceName in permissionsMapping) { + switch (permissionsMapping[serviceName]) { + case STATUS.YES: + grantPermissions.push(serviceName); + break; + case STATUS.NO: + revokePermissions.push(serviceName); + break; + case STATUS.UNSET: + resetPermissions.push(serviceName); + break; + default: + log.warn(`${serviceName} does not support ${permissionsMapping[serviceName]}. Please specify yes, no or reset.`); + }; + } + + log.debug(`Granting permissions for ${bundleId} as ${JSON.stringify(grantPermissions)}`); + log.debug(`Revoking permissions for ${bundleId} as ${JSON.stringify(revokePermissions)}`); + log.debug(`Resetting permissions for ${bundleId} as ${JSON.stringify(resetPermissions)}`); + + for (const action of grantPermissions) { + await simctl.grantPermission(bundleId, action); + } + + for (const action of revokePermissions) { + await simctl.revokePermission(bundleId, action); + } + + for (const action of resetPermissions) { + await simctl.resetPermission(bundleId, action); + } + + return true; } /** @@ -145,8 +148,7 @@ const extensions = {}; /** * Sets the particular permission to the application bundle. See - * https://github.com/wix/AppleSimulatorUtils for more details on - * the available service names and statuses. + * xcrun simctl privacy for more details on the available service names and statuses. * * @param {string} bundleId - Application bundle identifier. * @param {string} permission - Service name to be set. @@ -163,13 +165,12 @@ extensions.setPermission = async function setPermission (bundleId, permission, v * @param {string} bundleId - Application bundle identifier. * @param {Object} permissionsMapping - A mapping where kays * are service names and values are their corresponding status values. - * See https://github.com/wix/AppleSimulatorUtils - * for more details on available service names and statuses. + * See `xcrun simctl privacy` for more details on available service names and statuses. * @throws {Error} If there was an error while changing permissions. */ extensions.setPermissions = async function setPermissions (bundleId, permissionsMapping) { log.debug(`Setting access for '${bundleId}': ${JSON.stringify(permissionsMapping, null, 2)}`); - await setAccess(this.udid, bundleId, permissionsMapping); + await setAccess(this.simctl, bundleId, permissionsMapping); }; /** diff --git a/lib/simulator-xcode-9.js b/lib/simulator-xcode-9.js index b3578b1..38c6f1c 100644 --- a/lib/simulator-xcode-9.js +++ b/lib/simulator-xcode-9.js @@ -23,6 +23,7 @@ const DOMAIN_KEYBOARD_PREFERENCES = 'com.apple.keyboard.preferences'; // com.apple.locationd: translates system prompts for location // com.apple.tccd: translates system prompts for camera, microphone, contact, photos and app tracking transparency const SERVICES_FOR_TRANSLATION = ['com.apple.SpringBoard', 'com.apple.locationd', 'com.apple.tccd']; +const GLOBAL_PREFS_PLIST = '.GlobalPreferences.plist'; /** * Creates device and common Simulator preferences, which could @@ -566,9 +567,20 @@ class SimulatorXcode9 extends SimulatorXcode8 { return false; } + let previousAppleLanguages = null; + if (globalPrefs.AppleLanguages) { + const absolutePrefsPath = path.join(this.getDir(), 'Library', 'Preferences', GLOBAL_PREFS_PLIST); + try { + const {stdout} = await exec('plutil', ['-convert', 'json', absolutePrefsPath, '-o', '-']); + previousAppleLanguages = JSON.parse(stdout).AppleLanguages; + } catch (e) { + log.debug(`Cannot retrieve the current value of the 'AppleLanguages' preference: ${e.message}`); + } + } + const argChunks = generateDefaultsCommandArgs(globalPrefs, true); await B.all(argChunks.map((args) => this.simctl.spawnProcess([ - 'defaults', 'write', '.GlobalPreferences.plist', ...args + 'defaults', 'write', GLOBAL_PREFS_PLIST, ...args ]))); if (keyboard && keyboardId) { @@ -582,14 +594,34 @@ class SimulatorXcode9 extends SimulatorXcode8 { ]))); } + log.info('==============1'); + log.info(previousAppleLanguages); + log.info(globalPrefs.AppleLanguages); + + log.info('==============2'); + log.info(JSON.stringify(previousAppleLanguages)); + log.info(JSON.stringify(globalPrefs.AppleLanguages)); + if (globalPrefs.AppleLanguages) { - await B.all(SERVICES_FOR_TRANSLATION.map((arg) => this.simctl.spawnProcess([ - 'launchctl', 'stop', arg - ]))); + if (previousAppleLanguages && _.isEqual(previousAppleLanguages, globalPrefs.AppleLanguages)) { + log.info( + `The 'AppleLanguages' preference is already set to '${globalPrefs.AppleLanguages}'. ` + + `Skipping services reset` + ); + } else { + log.info( + `Will restart the following services in order to sync UI dialogs translation: ` + + `${SERVICES_FOR_TRANSLATION}. This might have unexpected side effects, ` + + `see https://github.com/appium/appium/issues/19440 for more details` + ); + await B.all(SERVICES_FOR_TRANSLATION.map((arg) => this.simctl.spawnProcess([ + 'launchctl', 'stop', arg + ]))); + } } return true; } } -export default SimulatorXcode9; +export default SimulatorXcode9; \ No newline at end of file From e5204c0039911809ce0ebb2bd9950a0419ce719b Mon Sep 17 00:00:00 2001 From: Kazuaki Matsuo Date: Sat, 25 Nov 2023 00:59:43 -0800 Subject: [PATCH 02/15] add type --- lib/extensions/permissions.js | 2 +- lib/simulator-xcode-9.js | 14 +++++--------- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/lib/extensions/permissions.js b/lib/extensions/permissions.js index 134a6b2..aa54ec1 100644 --- a/lib/extensions/permissions.js +++ b/lib/extensions/permissions.js @@ -56,7 +56,7 @@ async function execSQLiteQuery (db, query) { /** * Sets permissions for the given application * - * @param {Object} simctl - simctl object + * @param {import('node-simctl').Simctl} simctl - node-simctl object. * @param {string} bundleId - bundle identifier of the target application. * @param {Object} permissionsMapping - An object, where keys are service names * and values are corresponding state values. See the result of `xcrun simctl privacy` diff --git a/lib/simulator-xcode-9.js b/lib/simulator-xcode-9.js index 38c6f1c..46c8a26 100644 --- a/lib/simulator-xcode-9.js +++ b/lib/simulator-xcode-9.js @@ -498,6 +498,8 @@ class SimulatorXcode9 extends SimulatorXcode8 { /** * @typedef {Object} LanguageOptions * @property {string} name The name of the language, for example `de` or `zh-Hant-CN` + * @property {boolean} [skipSyncUiDialogTranslation] no Simulator services will be reset if this option is set to true. + * See https://github.com/appium/appium/issues/19440 for more details */ /** @@ -594,20 +596,14 @@ class SimulatorXcode9 extends SimulatorXcode8 { ]))); } - log.info('==============1'); - log.info(previousAppleLanguages); - log.info(globalPrefs.AppleLanguages); - - log.info('==============2'); - log.info(JSON.stringify(previousAppleLanguages)); - log.info(JSON.stringify(globalPrefs.AppleLanguages)); - if (globalPrefs.AppleLanguages) { - if (previousAppleLanguages && _.isEqual(previousAppleLanguages, globalPrefs.AppleLanguages)) { + if (_.isEqual(previousAppleLanguages, globalPrefs.AppleLanguages)) { log.info( `The 'AppleLanguages' preference is already set to '${globalPrefs.AppleLanguages}'. ` + `Skipping services reset` ); + } else if (language?.skipSyncUiDialogTranslation) { + log.info('Skipping services reset as requested. This might leave some system UI alerts untranslated'); } else { log.info( `Will restart the following services in order to sync UI dialogs translation: ` + From 37bdadf18ff5904213f7008f4067ab064a662f8b Mon Sep 17 00:00:00 2001 From: Kazuaki Matsuo Date: Sat, 25 Nov 2023 01:03:51 -0800 Subject: [PATCH 03/15] tweak --- lib/simulator-xcode-9.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/simulator-xcode-9.js b/lib/simulator-xcode-9.js index 46c8a26..042014c 100644 --- a/lib/simulator-xcode-9.js +++ b/lib/simulator-xcode-9.js @@ -620,4 +620,4 @@ class SimulatorXcode9 extends SimulatorXcode8 { } } -export default SimulatorXcode9; \ No newline at end of file +export default SimulatorXcode9; From 0c8c6a3f79ff86bd9fee75ad0647bebbdfcd756a Mon Sep 17 00:00:00 2001 From: Kazuaki Matsuo Date: Sat, 25 Nov 2023 01:06:08 -0800 Subject: [PATCH 04/15] remove wix links --- NOTICE.txt | 3 --- README.md | 1 - 2 files changed, 4 deletions(-) diff --git a/NOTICE.txt b/NOTICE.txt index 8b49ebd..a703b14 100644 --- a/NOTICE.txt +++ b/NOTICE.txt @@ -1,5 +1,2 @@ -This product uses [applesimutils](https://github.com/wix/AppleSimulatorUtils) software written by -Copyright 2017 Wix Engineering under MIT license - This product uses [idb](https://github.com/facebook/idb) software written by Copyright (c) 2019-present, Facebook, Inc. under MIT License diff --git a/README.md b/README.md index 6bfe5ee..c453128 100644 --- a/README.md +++ b/README.md @@ -37,7 +37,6 @@ The following tools and utilities are not mandatory, but could be used by the ap - [Mobile Native Foundation](https://github.com/MobileNativeFoundation) - [IDB](https://github.com/facebook/idb) -- [AppleSimulatorUtils](https://github.com/wix/AppleSimulatorUtils) ### Xcode and iOS versions From b1fcaeb98fa356e5895b486f105457ddab9ee5eb Mon Sep 17 00:00:00 2001 From: Kazuaki Matsuo Date: Sat, 25 Nov 2023 01:06:56 -0800 Subject: [PATCH 05/15] fix test --- .github/workflows/functional-test.yml | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/.github/workflows/functional-test.yml b/.github/workflows/functional-test.yml index 58c5da9..a597de0 100644 --- a/.github/workflows/functional-test.yml +++ b/.github/workflows/functional-test.yml @@ -36,11 +36,7 @@ jobs: - uses: maxim-lobanov/setup-xcode@v1 with: xcode-version: "${{ matrix.xcodeVersion }}" - - run: | - brew update - brew tap wix/brew - brew install applesimutils - xcrun simctl list devices available + - run: xcrun simctl list devices available name: Install Utilities - run: npm install name: Install dev dependencies From 2eff9b6d8ccb1d37be02adfc9f05924400752ef4 Mon Sep 17 00:00:00 2001 From: Kazuaki Matsuo Date: Sat, 25 Nov 2023 01:30:59 -0800 Subject: [PATCH 06/15] add function test --- test/functional/simulator-e2e-specs.js | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/test/functional/simulator-e2e-specs.js b/test/functional/simulator-e2e-specs.js index 99e08f2..e7cfe39 100644 --- a/test/functional/simulator-e2e-specs.js +++ b/test/functional/simulator-e2e-specs.js @@ -349,6 +349,17 @@ describe('advanced features', function () { }); }); }); + + describe('Permission', function () { + it('should set and get', async function () { + await sim.setPermission('com.apple.Maps', 'microphone', 'yes'); + await sim.getPermission('com.apple.Maps', 'microphone').should.eventually.eql('yes'); + await sim.setPermission('com.apple.Maps', 'microphone', 'no'); + await sim.getPermission('com.apple.Maps', 'microphone').should.eventually.eql('no'); + await sim.setPermission('com.apple.Maps', 'microphone', 'unset'); + await sim.getPermission('com.apple.Maps', 'microphone').should.eventually.eql('unset'); + }); + }); }); describe(`multiple instances of ${OS_VERSION} simulator on Xcode9+`, function () { From c6aa895942ca0730308460d39969f69f2c1d9614 Mon Sep 17 00:00:00 2001 From: Kazuaki Matsuo Date: Sat, 25 Nov 2023 10:26:16 -0800 Subject: [PATCH 07/15] use wix method for some services --- .github/workflows/functional-test.yml | 6 +- NOTICE.txt | 3 + README.md | 2 + lib/extensions/permissions.js | 132 +++++++++++++++++++------ test/functional/simulator-e2e-specs.js | 15 ++- 5 files changed, 128 insertions(+), 30 deletions(-) diff --git a/.github/workflows/functional-test.yml b/.github/workflows/functional-test.yml index a597de0..58c5da9 100644 --- a/.github/workflows/functional-test.yml +++ b/.github/workflows/functional-test.yml @@ -36,7 +36,11 @@ jobs: - uses: maxim-lobanov/setup-xcode@v1 with: xcode-version: "${{ matrix.xcodeVersion }}" - - run: xcrun simctl list devices available + - run: | + brew update + brew tap wix/brew + brew install applesimutils + xcrun simctl list devices available name: Install Utilities - run: npm install name: Install dev dependencies diff --git a/NOTICE.txt b/NOTICE.txt index a703b14..8b49ebd 100644 --- a/NOTICE.txt +++ b/NOTICE.txt @@ -1,2 +1,5 @@ +This product uses [applesimutils](https://github.com/wix/AppleSimulatorUtils) software written by +Copyright 2017 Wix Engineering under MIT license + This product uses [idb](https://github.com/facebook/idb) software written by Copyright (c) 2019-present, Facebook, Inc. under MIT License diff --git a/README.md b/README.md index c453128..6aaa8b6 100644 --- a/README.md +++ b/README.md @@ -37,6 +37,8 @@ The following tools and utilities are not mandatory, but could be used by the ap - [Mobile Native Foundation](https://github.com/MobileNativeFoundation) - [IDB](https://github.com/facebook/idb) +- [AppleSimulatorUtils](https://github.com/wix/AppleSimulatorUtils) + - For `contacts`, `camera`, `faceid`, `health`, `homekit`, `notifications`, `speech` and `userTracking` permissions ### Xcode and iOS versions diff --git a/lib/extensions/permissions.js b/lib/extensions/permissions.js index aa54ec1..77b7cae 100644 --- a/lib/extensions/permissions.js +++ b/lib/extensions/permissions.js @@ -1,7 +1,9 @@ import log from '../logger'; import _ from 'lodash'; +import { fs, util } from '@appium/support'; import { exec } from 'teen_process'; import path from 'path'; +import B from 'bluebird'; const STATUS = Object.freeze({ UNSET: 'unset', @@ -10,6 +12,22 @@ const STATUS = Object.freeze({ LIMITED: 'limited', }); +const WIX_SIM_UTILS = 'applesimutils'; + +const WIX_SERVICE_NAMES = [ + // 'contacts' permission does not work well with the privacy command while the help description addresses the command support 'contacts' + // Confirmed up to with Xcode 15 and iOS 17 + `contacts`, + + // Below services have not supported by the privacy command by Apple yet. + 'camera', + 'faceid', + 'health', + 'homekit', + 'notifications', + 'speech', + 'userTracking' +]; const SERVICES = Object.freeze({ calendar: 'kTCCServiceCalendar', @@ -35,6 +53,10 @@ function toInternalServiceName (serviceName) { ); } +function formatStatus (status) { + return [STATUS.UNSET, STATUS.NO].includes(status) ? _.toUpper(status) : status; +} + /** * Runs a command line sqlite3 query * @@ -53,51 +75,104 @@ async function execSQLiteQuery (db, query) { } } +async function execWix (args) { + try { + await fs.which(WIX_SIM_UTILS); + } catch (e) { + throw new Error( + `${WIX_SIM_UTILS} binary has not been found in your PATH. ` + + `Please install it ('brew tap wix/brew && brew install wix/brew/applesimutils') to ` + + `be able to change application permissions` + ); + } + + log.debug(`Executing: ${WIX_SIM_UTILS} ${util.quote(args)}`); + try { + const {stdout} = await exec(WIX_SIM_UTILS, args); + log.debug(`Command output: ${stdout}`); + return stdout; + } catch (e) { + throw new Error(`Cannot execute "${WIX_SIM_UTILS} ${util.quote(args)}". Original error: ${e.stderr || e.message}`); + } +} + /** * Sets permissions for the given application * * @param {import('node-simctl').Simctl} simctl - node-simctl object. + * @param {string} udid - udid of the target simulator device. * @param {string} bundleId - bundle identifier of the target application. * @param {Object} permissionsMapping - An object, where keys are service names - * and values are corresponding state values. See the result of `xcrun simctl privacy` + * and values are corresponding state values. Services listed in WIX_SERVICE_NAMES + * will be set by WIX_SIM_UTILS otherwise via `xcrun simctl privacy` command by Apple. + * See the result of `xcrun simctl privacy` and https://github.com/wix/AppleSimulatorUtils * for more details on available service names and statuses. * @throws {Error} If there was an error while changing permissions. */ -async function setAccess (simctl, bundleId, permissionsMapping) { +async function setAccess (simctl, udid, bundleId, permissionsMapping) { + const wixPermissions = {}; + const grantPermissions = []; const revokePermissions = []; const resetPermissions = []; for (const serviceName in permissionsMapping) { - switch (permissionsMapping[serviceName]) { - case STATUS.YES: - grantPermissions.push(serviceName); - break; - case STATUS.NO: - revokePermissions.push(serviceName); - break; - case STATUS.UNSET: - resetPermissions.push(serviceName); - break; - default: - log.warn(`${serviceName} does not support ${permissionsMapping[serviceName]}. Please specify yes, no or reset.`); - }; + if (WIX_SERVICE_NAMES.includes(serviceName)) { + wixPermissions[serviceName] = permissionsMapping[serviceName]; + } else { + switch (permissionsMapping[serviceName]) { + case STATUS.YES: + grantPermissions.push(serviceName); + break; + case STATUS.NO: + revokePermissions.push(serviceName); + break; + case STATUS.UNSET: + resetPermissions.push(serviceName); + break; + default: + log.warn(`${serviceName} does not support ${permissionsMapping[serviceName]}. Please specify yes, no or unset.`); + }; + } } - log.debug(`Granting permissions for ${bundleId} as ${JSON.stringify(grantPermissions)}`); - log.debug(`Revoking permissions for ${bundleId} as ${JSON.stringify(revokePermissions)}`); - log.debug(`Resetting permissions for ${bundleId} as ${JSON.stringify(resetPermissions)}`); + let permissionPromises = []; - for (const action of grantPermissions) { - await simctl.grantPermission(bundleId, action); + if (!_.isEmpty(grantPermissions)) { + log.debug(`Granting permissions for ${bundleId} as ${JSON.stringify(grantPermissions)}`); + for (const action of grantPermissions) { + permissionPromises.push(simctl.grantPermission(bundleId, action)); + } } - for (const action of revokePermissions) { - await simctl.revokePermission(bundleId, action); + if (!_.isEmpty(revokePermissions)) { + log.debug(`Revoking permissions for ${bundleId} as ${JSON.stringify(grantPermissions)}`); + for (const action of revokePermissions) { + permissionPromises.push(simctl.revokePermission(bundleId, action)); + } } - for (const action of resetPermissions) { - await simctl.resetPermission(bundleId, action); + if (!_.isEmpty(resetPermissions)) { + log.debug(`Revoking permissions for ${bundleId} as ${JSON.stringify(resetPermissions)}`); + for (const action of resetPermissions) { + permissionPromises.push(simctl.resetPermission(bundleId, action)); + } + } + + if (!_.isEmpty(permissionPromises)) { + B.all(permissionPromises); + } + + if (!_.isEmpty(wixPermissions)) { + log.debug(`Setting permissions for ${bundleId} wit ${WIX_SIM_UTILS} as ${JSON.stringify(wixPermissions)}`); + const permissionsArg = _.toPairs(wixPermissions) + .map((x) => `${x[0]}=${formatStatus(x[1])}`) + .join(','); + await execWix([ + '--byId', udid, + '--bundle', bundleId, + '--setPermissions', permissionsArg, + ]); } return true; @@ -147,8 +222,8 @@ async function getAccess (bundleId, serviceName, simDataRoot) { const extensions = {}; /** - * Sets the particular permission to the application bundle. See - * xcrun simctl privacy for more details on the available service names and statuses. + * Sets the particular permission to the application bundle. See https://github.com/wix/AppleSimulatorUtils + * or `xcrun simctl privacy` for more details on the available service names and statuses. * * @param {string} bundleId - Application bundle identifier. * @param {string} permission - Service name to be set. @@ -165,12 +240,13 @@ extensions.setPermission = async function setPermission (bundleId, permission, v * @param {string} bundleId - Application bundle identifier. * @param {Object} permissionsMapping - A mapping where kays * are service names and values are their corresponding status values. - * See `xcrun simctl privacy` for more details on available service names and statuses. + * See https://github.com/wix/AppleSimulatorUtils or `xcrun simctl privacy` + * for more details on available service names and statuses. * @throws {Error} If there was an error while changing permissions. */ extensions.setPermissions = async function setPermissions (bundleId, permissionsMapping) { log.debug(`Setting access for '${bundleId}': ${JSON.stringify(permissionsMapping, null, 2)}`); - await setAccess(this.simctl, bundleId, permissionsMapping); + await setAccess(this.simctl, this.udid, bundleId, permissionsMapping); }; /** diff --git a/test/functional/simulator-e2e-specs.js b/test/functional/simulator-e2e-specs.js index e7cfe39..9e5a494 100644 --- a/test/functional/simulator-e2e-specs.js +++ b/test/functional/simulator-e2e-specs.js @@ -351,7 +351,7 @@ describe('advanced features', function () { }); describe('Permission', function () { - it('should set and get', async function () { + it('should set and get with simctrl privacy command', async function () { await sim.setPermission('com.apple.Maps', 'microphone', 'yes'); await sim.getPermission('com.apple.Maps', 'microphone').should.eventually.eql('yes'); await sim.setPermission('com.apple.Maps', 'microphone', 'no'); @@ -359,6 +359,19 @@ describe('advanced features', function () { await sim.setPermission('com.apple.Maps', 'microphone', 'unset'); await sim.getPermission('com.apple.Maps', 'microphone').should.eventually.eql('unset'); }); + + it('should set and get with wix command', async function () { + await sim.setPermission('com.apple.Maps', 'contacts', 'yes'); + await sim.getPermission('com.apple.Maps', 'contacts').should.eventually.eql('yes'); + await sim.setPermission('com.apple.Maps', 'contacts', 'no'); + await sim.getPermission('com.apple.Maps', 'contacts').should.eventually.eql('no'); + + // unset sets as 'no' + await sim.setPermission('com.apple.Maps', 'contacts', 'yes'); + await sim.getPermission('com.apple.Maps', 'contacts').should.eventually.eql('yes'); + await sim.setPermission('com.apple.Maps', 'contacts', 'unset'); + await sim.getPermission('com.apple.Maps', 'contacts').should.eventually.eql('no'); + }); }); }); From 7099dec5bd6bd48e892ce5843d78c34612ceb66c Mon Sep 17 00:00:00 2001 From: Kazuaki Matsuo Date: Sat, 25 Nov 2023 10:40:46 -0800 Subject: [PATCH 08/15] chore: use whilelist --- lib/extensions/permissions.js | 27 +++++++++++--------------- test/functional/simulator-e2e-specs.js | 10 ++++------ 2 files changed, 15 insertions(+), 22 deletions(-) diff --git a/lib/extensions/permissions.js b/lib/extensions/permissions.js index 77b7cae..f6dda1b 100644 --- a/lib/extensions/permissions.js +++ b/lib/extensions/permissions.js @@ -14,19 +14,13 @@ const STATUS = Object.freeze({ const WIX_SIM_UTILS = 'applesimutils'; -const WIX_SERVICE_NAMES = [ - // 'contacts' permission does not work well with the privacy command while the help description addresses the command support 'contacts' - // Confirmed up to with Xcode 15 and iOS 17 - `contacts`, - - // Below services have not supported by the privacy command by Apple yet. - 'camera', - 'faceid', - 'health', - 'homekit', - 'notifications', - 'speech', - 'userTracking' +// `location` permission does not work with WIX/applesimutils. +// Note that except for 'contacts', the Apple's privacy command sets +// permissions properly but it resets the app process while WIX/applesimutils does not +// reset the app process. +const APPLE_PRIVACY_CMD = [ + 'location', + 'location-always' ]; const SERVICES = Object.freeze({ @@ -103,10 +97,11 @@ async function execWix (args) { * @param {string} udid - udid of the target simulator device. * @param {string} bundleId - bundle identifier of the target application. * @param {Object} permissionsMapping - An object, where keys are service names - * and values are corresponding state values. Services listed in WIX_SERVICE_NAMES - * will be set by WIX_SIM_UTILS otherwise via `xcrun simctl privacy` command by Apple. + * and values are corresponding state values. Services listed in APPLE_PRIVACY_CMD + * will be set with `xcrun simctl privacy` command by Apple otherwise AppleSimulatorUtils by WIX. * See the result of `xcrun simctl privacy` and https://github.com/wix/AppleSimulatorUtils * for more details on available service names and statuses. + * Note that the `xcrun simctl privacy` command kill the app process. * @throws {Error} If there was an error while changing permissions. */ async function setAccess (simctl, udid, bundleId, permissionsMapping) { @@ -117,7 +112,7 @@ async function setAccess (simctl, udid, bundleId, permissionsMapping) { const resetPermissions = []; for (const serviceName in permissionsMapping) { - if (WIX_SERVICE_NAMES.includes(serviceName)) { + if (!APPLE_PRIVACY_CMD.includes(serviceName)) { wixPermissions[serviceName] = permissionsMapping[serviceName]; } else { switch (permissionsMapping[serviceName]) { diff --git a/test/functional/simulator-e2e-specs.js b/test/functional/simulator-e2e-specs.js index 9e5a494..3106fda 100644 --- a/test/functional/simulator-e2e-specs.js +++ b/test/functional/simulator-e2e-specs.js @@ -352,12 +352,10 @@ describe('advanced features', function () { describe('Permission', function () { it('should set and get with simctrl privacy command', async function () { - await sim.setPermission('com.apple.Maps', 'microphone', 'yes'); - await sim.getPermission('com.apple.Maps', 'microphone').should.eventually.eql('yes'); - await sim.setPermission('com.apple.Maps', 'microphone', 'no'); - await sim.getPermission('com.apple.Maps', 'microphone').should.eventually.eql('no'); - await sim.setPermission('com.apple.Maps', 'microphone', 'unset'); - await sim.getPermission('com.apple.Maps', 'microphone').should.eventually.eql('unset'); + // no exceptions + await sim.setPermission('com.apple.Maps', 'location', 'yes').should.eventually.be.true; + await sim.setPermission('com.apple.Maps', 'location', 'no').should.eventually.be.true; + await sim.setPermission('com.apple.Maps', 'location', 'unset').should.eventually.be.true; }); it('should set and get with wix command', async function () { From 1d8d0955eb860a582aecdf16e3688fa73161666b Mon Sep 17 00:00:00 2001 From: Kazuaki Matsuo Date: Sat, 25 Nov 2023 10:43:09 -0800 Subject: [PATCH 09/15] modify comment --- lib/extensions/permissions.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/extensions/permissions.js b/lib/extensions/permissions.js index f6dda1b..11afb75 100644 --- a/lib/extensions/permissions.js +++ b/lib/extensions/permissions.js @@ -16,8 +16,9 @@ const WIX_SIM_UTILS = 'applesimutils'; // `location` permission does not work with WIX/applesimutils. // Note that except for 'contacts', the Apple's privacy command sets -// permissions properly but it resets the app process while WIX/applesimutils does not -// reset the app process. +// permissions properly but it kills the app process while WIX/applesimutils does not. +// In the backward compatibility perspective, +// we'd like to keep the app process as possible. const APPLE_PRIVACY_CMD = [ 'location', 'location-always' From 1a9bbce9c94ec32ce324a00a071d6914309dc54e Mon Sep 17 00:00:00 2001 From: Kazuaki Matsuo Date: Sat, 25 Nov 2023 11:24:38 -0800 Subject: [PATCH 10/15] should not be rejected --- test/functional/simulator-e2e-specs.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/functional/simulator-e2e-specs.js b/test/functional/simulator-e2e-specs.js index 3106fda..2ab42cd 100644 --- a/test/functional/simulator-e2e-specs.js +++ b/test/functional/simulator-e2e-specs.js @@ -353,9 +353,9 @@ describe('advanced features', function () { describe('Permission', function () { it('should set and get with simctrl privacy command', async function () { // no exceptions - await sim.setPermission('com.apple.Maps', 'location', 'yes').should.eventually.be.true; - await sim.setPermission('com.apple.Maps', 'location', 'no').should.eventually.be.true; - await sim.setPermission('com.apple.Maps', 'location', 'unset').should.eventually.be.true; + expect(await sim.setPermission('com.apple.Maps', 'location', 'yes')).should.not.rejected; + expect(await sim.setPermission('com.apple.Maps', 'location', 'no')).should.not.rejected; + expect(await sim.setPermission('com.apple.Maps', 'location', 'unset')).should.not.rejected; }); it('should set and get with wix command', async function () { From c1fef21d571e27eba09772b1f720a1c4bf396ada Mon Sep 17 00:00:00 2001 From: Kazuaki Matsuo Date: Sat, 25 Nov 2023 11:59:26 -0800 Subject: [PATCH 11/15] tweak plauralize, tests and naming --- lib/extensions/permissions.js | 20 ++++++++++---------- test/functional/simulator-e2e-specs.js | 6 +++--- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/lib/extensions/permissions.js b/lib/extensions/permissions.js index 11afb75..9fb9c49 100644 --- a/lib/extensions/permissions.js +++ b/lib/extensions/permissions.js @@ -19,7 +19,7 @@ const WIX_SIM_UTILS = 'applesimutils'; // permissions properly but it kills the app process while WIX/applesimutils does not. // In the backward compatibility perspective, // we'd like to keep the app process as possible. -const APPLE_PRIVACY_CMD = [ +const PERMISSIONS_APPLIED_VIA_SIMCTL = [ 'location', 'location-always' ]; @@ -98,7 +98,7 @@ async function execWix (args) { * @param {string} udid - udid of the target simulator device. * @param {string} bundleId - bundle identifier of the target application. * @param {Object} permissionsMapping - An object, where keys are service names - * and values are corresponding state values. Services listed in APPLE_PRIVACY_CMD + * and values are corresponding state values. Services listed in PERMISSIONS_APPLIED_VIA_SIMCTL * will be set with `xcrun simctl privacy` command by Apple otherwise AppleSimulatorUtils by WIX. * See the result of `xcrun simctl privacy` and https://github.com/wix/AppleSimulatorUtils * for more details on available service names and statuses. @@ -108,12 +108,12 @@ async function execWix (args) { async function setAccess (simctl, udid, bundleId, permissionsMapping) { const wixPermissions = {}; - const grantPermissions = []; - const revokePermissions = []; - const resetPermissions = []; + const /** @type {string[]} */ grantPermissions = []; + const /** @type {string[]} */ revokePermissions = []; + const /** @type {string[]} */ resetPermissions = []; for (const serviceName in permissionsMapping) { - if (!APPLE_PRIVACY_CMD.includes(serviceName)) { + if (!PERMISSIONS_APPLIED_VIA_SIMCTL.includes(serviceName)) { wixPermissions[serviceName] = permissionsMapping[serviceName]; } else { switch (permissionsMapping[serviceName]) { @@ -135,28 +135,28 @@ async function setAccess (simctl, udid, bundleId, permissionsMapping) { let permissionPromises = []; if (!_.isEmpty(grantPermissions)) { - log.debug(`Granting permissions for ${bundleId} as ${JSON.stringify(grantPermissions)}`); + log.debug(`Granting ${util.pluralize('permission', grantPermissions.length, false)} for ${bundleId}: ${grantPermissions}`); for (const action of grantPermissions) { permissionPromises.push(simctl.grantPermission(bundleId, action)); } } if (!_.isEmpty(revokePermissions)) { - log.debug(`Revoking permissions for ${bundleId} as ${JSON.stringify(grantPermissions)}`); + log.debug(`Revoking ${util.pluralize('permission', revokePermissions.length, false)} for ${bundleId}: ${revokePermissions}`); for (const action of revokePermissions) { permissionPromises.push(simctl.revokePermission(bundleId, action)); } } if (!_.isEmpty(resetPermissions)) { - log.debug(`Revoking permissions for ${bundleId} as ${JSON.stringify(resetPermissions)}`); + log.debug(`Resetting ${util.pluralize('permission', resetPermissions.length, false)} for ${bundleId}: ${resetPermissions}`); for (const action of resetPermissions) { permissionPromises.push(simctl.resetPermission(bundleId, action)); } } if (!_.isEmpty(permissionPromises)) { - B.all(permissionPromises); + await B.all(permissionPromises); } if (!_.isEmpty(wixPermissions)) { diff --git a/test/functional/simulator-e2e-specs.js b/test/functional/simulator-e2e-specs.js index 2ab42cd..b90f52a 100644 --- a/test/functional/simulator-e2e-specs.js +++ b/test/functional/simulator-e2e-specs.js @@ -353,9 +353,9 @@ describe('advanced features', function () { describe('Permission', function () { it('should set and get with simctrl privacy command', async function () { // no exceptions - expect(await sim.setPermission('com.apple.Maps', 'location', 'yes')).should.not.rejected; - expect(await sim.setPermission('com.apple.Maps', 'location', 'no')).should.not.rejected; - expect(await sim.setPermission('com.apple.Maps', 'location', 'unset')).should.not.rejected; + expect(await sim.setPermission('com.apple.Maps', 'location', 'yes')).should.not.be.rejected; + expect(await sim.setPermission('com.apple.Maps', 'location', 'no')).should.not.be.rejected; + expect(await sim.setPermission('com.apple.Maps', 'location', 'unset')).should.not.be.rejected; }); it('should set and get with wix command', async function () { From 801240603622389e352361e8c5bc38864d74d1cb Mon Sep 17 00:00:00 2001 From: Kazuaki Matsuo Date: Sat, 25 Nov 2023 12:00:29 -0800 Subject: [PATCH 12/15] add error case --- lib/extensions/permissions.js | 2 +- test/functional/simulator-e2e-specs.js | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/extensions/permissions.js b/lib/extensions/permissions.js index 9fb9c49..8efeb84 100644 --- a/lib/extensions/permissions.js +++ b/lib/extensions/permissions.js @@ -127,7 +127,7 @@ async function setAccess (simctl, udid, bundleId, permissionsMapping) { resetPermissions.push(serviceName); break; default: - log.warn(`${serviceName} does not support ${permissionsMapping[serviceName]}. Please specify yes, no or unset.`); + log.errorAndThrow(`${serviceName} does not support ${permissionsMapping[serviceName]}. Please specify 'yes', 'no' or 'unset'.`); }; } } diff --git a/test/functional/simulator-e2e-specs.js b/test/functional/simulator-e2e-specs.js index b90f52a..23bfa54 100644 --- a/test/functional/simulator-e2e-specs.js +++ b/test/functional/simulator-e2e-specs.js @@ -356,6 +356,8 @@ describe('advanced features', function () { expect(await sim.setPermission('com.apple.Maps', 'location', 'yes')).should.not.be.rejected; expect(await sim.setPermission('com.apple.Maps', 'location', 'no')).should.not.be.rejected; expect(await sim.setPermission('com.apple.Maps', 'location', 'unset')).should.not.be.rejected; + + expect(await sim.setPermission('com.apple.Maps', 'location', 'unsupported')).should.be.rejected; }); it('should set and get with wix command', async function () { From 56bd0bea068733dbb21acdc62283d49b7e0eb773 Mon Sep 17 00:00:00 2001 From: Kazuaki Matsuo Date: Sat, 25 Nov 2023 12:22:07 -0800 Subject: [PATCH 13/15] test: adjust syntax --- test/functional/simulator-e2e-specs.js | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/test/functional/simulator-e2e-specs.js b/test/functional/simulator-e2e-specs.js index 23bfa54..e036f53 100644 --- a/test/functional/simulator-e2e-specs.js +++ b/test/functional/simulator-e2e-specs.js @@ -353,11 +353,10 @@ describe('advanced features', function () { describe('Permission', function () { it('should set and get with simctrl privacy command', async function () { // no exceptions - expect(await sim.setPermission('com.apple.Maps', 'location', 'yes')).should.not.be.rejected; - expect(await sim.setPermission('com.apple.Maps', 'location', 'no')).should.not.be.rejected; - expect(await sim.setPermission('com.apple.Maps', 'location', 'unset')).should.not.be.rejected; - - expect(await sim.setPermission('com.apple.Maps', 'location', 'unsupported')).should.be.rejected; + expect(await sim.setPermission('com.apple.Maps', 'location', 'yes')).not.to.throw(); + expect(await sim.setPermission('com.apple.Maps', 'location', 'no')).not.to.throw(); + expect(await sim.setPermission('com.apple.Maps', 'location', 'unset')).not.to.throw(); + expect(await sim.setPermission('com.apple.Maps', 'location', 'unsupported')).to.throw(); }); it('should set and get with wix command', async function () { From d090392c1fe2e437a60958fab8a896c6bf1e1024 Mon Sep 17 00:00:00 2001 From: Kazuaki Matsuo Date: Sat, 25 Nov 2023 17:45:33 -0800 Subject: [PATCH 14/15] tune test --- lib/extensions/permissions.js | 4 ++-- test/functional/simulator-e2e-specs.js | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/extensions/permissions.js b/lib/extensions/permissions.js index 8efeb84..347cb8f 100644 --- a/lib/extensions/permissions.js +++ b/lib/extensions/permissions.js @@ -106,7 +106,7 @@ async function execWix (args) { * @throws {Error} If there was an error while changing permissions. */ async function setAccess (simctl, udid, bundleId, permissionsMapping) { - const wixPermissions = {}; + const /** @type {Record} */ wixPermissions = {}; const /** @type {string[]} */ grantPermissions = []; const /** @type {string[]} */ revokePermissions = []; @@ -132,7 +132,7 @@ async function setAccess (simctl, udid, bundleId, permissionsMapping) { } } - let permissionPromises = []; + const /** @type {string[]} */ permissionPromises = []; if (!_.isEmpty(grantPermissions)) { log.debug(`Granting ${util.pluralize('permission', grantPermissions.length, false)} for ${bundleId}: ${grantPermissions}`); diff --git a/test/functional/simulator-e2e-specs.js b/test/functional/simulator-e2e-specs.js index e036f53..a864f3d 100644 --- a/test/functional/simulator-e2e-specs.js +++ b/test/functional/simulator-e2e-specs.js @@ -353,10 +353,10 @@ describe('advanced features', function () { describe('Permission', function () { it('should set and get with simctrl privacy command', async function () { // no exceptions - expect(await sim.setPermission('com.apple.Maps', 'location', 'yes')).not.to.throw(); - expect(await sim.setPermission('com.apple.Maps', 'location', 'no')).not.to.throw(); - expect(await sim.setPermission('com.apple.Maps', 'location', 'unset')).not.to.throw(); - expect(await sim.setPermission('com.apple.Maps', 'location', 'unsupported')).to.throw(); + await expect(sim.setPermission('com.apple.Maps', 'location', 'yes')).not.to.be.rejected; + expect(await sim.setPermission('com.apple.Maps', 'location', 'no')).not.to.be.rejected; + expect(await sim.setPermission('com.apple.Maps', 'location', 'unset')).not.to.be.rejected; + await expect(sim.setPermission('com.apple.Maps', 'location', 'unsupported')).to.be.rejected; }); it('should set and get with wix command', async function () { From 76a6f8462ed9d0fbc14ec58eb5c10f83bf1a0d5c Mon Sep 17 00:00:00 2001 From: Kazuaki Matsuo Date: Sat, 25 Nov 2023 18:12:25 -0800 Subject: [PATCH 15/15] tune test code --- test/functional/simulator-e2e-specs.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/functional/simulator-e2e-specs.js b/test/functional/simulator-e2e-specs.js index a864f3d..77c1bd0 100644 --- a/test/functional/simulator-e2e-specs.js +++ b/test/functional/simulator-e2e-specs.js @@ -354,8 +354,8 @@ describe('advanced features', function () { it('should set and get with simctrl privacy command', async function () { // no exceptions await expect(sim.setPermission('com.apple.Maps', 'location', 'yes')).not.to.be.rejected; - expect(await sim.setPermission('com.apple.Maps', 'location', 'no')).not.to.be.rejected; - expect(await sim.setPermission('com.apple.Maps', 'location', 'unset')).not.to.be.rejected; + await expect(sim.setPermission('com.apple.Maps', 'location', 'no')).not.to.be.rejected; + await expect(sim.setPermission('com.apple.Maps', 'location', 'unset')).not.to.be.rejected; await expect(sim.setPermission('com.apple.Maps', 'location', 'unsupported')).to.be.rejected; });