Skip to content

Commit

Permalink
Merge pull request #4301 from wix/fix/ios17-permissions
Browse files Browse the repository at this point in the history
fix(iOS): migrate device commands to Simctl.
  • Loading branch information
asafkorem authored Jan 14, 2024
2 parents b9eb42c + 1dcfa6a commit 688d24f
Show file tree
Hide file tree
Showing 18 changed files with 719 additions and 193 deletions.
37 changes: 20 additions & 17 deletions detox/detox.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -644,10 +644,11 @@ declare global {
*/
selectApp(app: string): Promise<void>;

// TODO: document permissions types.
/**
* Launch the app.
*
* <p>For info regarding launch arguments, refer to the [dedicated guide](https://wix.github.io/Detox/docs/api/launch-args).
* <p>For info regarding launch arguments, refer to the [dedicated guide](https://wix.github.io/Detox/docs/guide/launch-args).
*
* @example
* // Terminate the app and launch it again. If set to false, the simulator will try to bring app from background,
Expand Down Expand Up @@ -1624,23 +1625,25 @@ declare global {
userTracking?: UserTrackingPermission;
}

type BasicPermissionState = 'YES' | 'NO' | 'unset';
type ExtendedPermissionState = 'YES' | 'NO' | 'unset' | 'limited';
type LocationPermission = 'always' | 'inuse' | 'never' | 'unset';
type PermissionState = 'YES' | 'NO' | 'unset';
type CameraPermission = PermissionState;
type ContactsPermission = PermissionState;
type CalendarPermission = PermissionState;
type HealthPermission = PermissionState;
type HomekitPermission = PermissionState;
type MediaLibraryPermission = PermissionState;
type MicrophonePermission = PermissionState;
type MotionPermission = PermissionState;
type PhotosPermission = PermissionState;
type RemindersPermission = PermissionState;
type SiriPermission = PermissionState;
type SpeechPermission = PermissionState;
type NotificationsPermission = PermissionState;
type FaceIDPermission = PermissionState;
type UserTrackingPermission = PermissionState;

type CameraPermission = BasicPermissionState;
type ContactsPermission = ExtendedPermissionState;
type CalendarPermission = BasicPermissionState;
type HealthPermission = BasicPermissionState;
type HomekitPermission = BasicPermissionState;
type MediaLibraryPermission = BasicPermissionState;
type MicrophonePermission = BasicPermissionState;
type MotionPermission = BasicPermissionState;
type PhotosPermission = ExtendedPermissionState;
type RemindersPermission = BasicPermissionState;
type SiriPermission = BasicPermissionState;
type SpeechPermission = BasicPermissionState;
type NotificationsPermission = BasicPermissionState;
type FaceIDPermission = BasicPermissionState;
type UserTrackingPermission = BasicPermissionState;

interface DeviceLaunchAppConfig {
/**
Expand Down
125 changes: 110 additions & 15 deletions detox/src/devices/common/drivers/ios/tools/AppleSimUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,21 +10,123 @@ const environment = require('../../../../../utils/environment');
const log = require('../../../../../utils/logger').child({ cat: 'device' });
const { quote } = require('../../../../../utils/shellQuote');

const PERMISSIONS_VALUES = {
YES: 'YES',
NO: 'NO',
UNSET: 'unset',
LIMITED: 'limited',
};

const SIMCTL_SET_PERMISSION_ACTIONS ={
GRANT: 'grant',
REVOKE: 'revoke',
RESET: 'reset',
};

class AppleSimUtils {
async setPermissions(udid, bundleId, permissionsObj) {
let permissions = [];
_.forEach(permissionsObj, function (shouldAllow, permission) {
permissions.push(permission + '=' + shouldAllow);
});
for (const [service, value] of Object.entries(permissionsObj)) {
switch (service) {
case 'location':
switch (value) {
case 'always':
await this.setPermissionWithSimctl(udid, bundleId, SIMCTL_SET_PERMISSION_ACTIONS.GRANT, 'location-always');
break;

case 'inuse':
await this.setPermissionWithSimctl(udid, bundleId, SIMCTL_SET_PERMISSION_ACTIONS.GRANT, 'location');
break;

case 'never':
await this.setPermissionWithSimctl(udid, bundleId, SIMCTL_SET_PERMISSION_ACTIONS.REVOKE, 'location');
break;

case 'unset':
await this.setPermissionWithSimctl(udid, bundleId, SIMCTL_SET_PERMISSION_ACTIONS.RESET, 'location');
break;
}

break;

case 'contacts':
if (value === PERMISSIONS_VALUES.LIMITED) {
await this.setPermissionWithSimctl(udid, bundleId, SIMCTL_SET_PERMISSION_ACTIONS.GRANT, 'contacts-limited');
} else {
await this.setPermissionWithAppleSimUtils(udid, bundleId, service, value);
}
break;

case 'photos':
if (value === PERMISSIONS_VALUES.LIMITED) {
await this.setPermissionWithSimctl(udid, bundleId, SIMCTL_SET_PERMISSION_ACTIONS.GRANT, 'photos-add');
} else {
await this.setPermissionWithAppleSimUtils(udid, bundleId, service, value);
}
break;

// eslint-disable-next-line no-fallthrough
case 'calendar':
case 'camera':
case 'medialibrary':
case 'microphone':
case 'motion':
case 'reminders':
case 'siri':
// Simctl uses kebab-case for service names.
const simctlService = service.replace('medialibrary', 'media-library');
await this.setPermissionWithSimctl(udid, bundleId, this.basicPermissionValueToSimctlAction(value), simctlService);
break;

// Requires AppleSimUtils: unsupported by latest Simctl at the moment of writing this code.
// eslint-disable-next-line no-fallthrough
case 'notifications':
case 'health':
case 'homekit':
case 'speech':
case 'faceid':
case 'userTracking':
await this.setPermissionWithAppleSimUtils(udid, bundleId, service, value);
break;
}
}
}

basicPermissionValueToSimctlAction(value) {
switch (value) {
case PERMISSIONS_VALUES.YES:
return SIMCTL_SET_PERMISSION_ACTIONS.GRANT;

case PERMISSIONS_VALUES.NO:
return SIMCTL_SET_PERMISSION_ACTIONS.REVOKE;

case PERMISSIONS_VALUES.UNSET:
return SIMCTL_SET_PERMISSION_ACTIONS.RESET;
}
}

async setPermissionWithSimctl(udid, bundleId, action, service) {
const options = {
args: `--byId ${udid} --bundle ${bundleId} --restartSB --setPermissions ${_.join(permissions, ',')}`,
cmd: `privacy ${udid} ${action} ${service} ${bundleId}`,
statusLogs: {
trying: `Trying to set permissions...`,
successful: 'Permissions are set'
trying: `Trying to set permissions with Simctl: ${action} ${service}...`,
successful: `${service} permissions are set`
},
retries: 1,
};

await this._execSimctl(options);
}

async setPermissionWithAppleSimUtils(udid, bundleId, service, value) {
const options = {
args: `--byId ${udid} --bundle ${bundleId} --restartSB --setPermissions ${service}=${value}`,
statusLogs: {
trying: `Trying to set permissions with AppleSimUtils: ${service}=${value}...`,
successful: `${service} permissions are set`
},
retries: 1,
};

await this._execAppleSimUtils(options);
}

Expand Down Expand Up @@ -319,14 +421,7 @@ class AppleSimUtils {
}

async setLocation(udid, lat, lon) {
const result = await childProcess.execWithRetriesAndLogs(`which fbsimctl`, { retries: 1 });
if (_.get(result, 'stdout')) {
await childProcess.execWithRetriesAndLogs(`fbsimctl ${udid} set_location ${lat} ${lon}`, { retries: 1 });
} else {
throw new DetoxRuntimeError(`setLocation currently supported only through fbsimctl.
Install fbsimctl using:
"brew tap facebook/fb && export CODE_SIGNING_REQUIRED=NO && brew install fbsimctl"`);
}
await this._execSimctl({ cmd: `location ${udid} set ${lat},${lon}` });
}

async resetContentAndSettings(udid) {
Expand Down
1 change: 1 addition & 0 deletions detox/test/android/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ dependencies {
implementation project(':react-native-community-geolocation')
implementation project(':react-native-datetimepicker')
implementation project(':react-native-launcharguments')
implementation project(':react-native-permissions')

androidTestImplementation(project(path: ':detox'))
androidTestImplementation 'com.linkedin.testbutler:test-butler-library:2.2.1'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import com.reactnativecommunity.slider.ReactSliderPackage;
import com.reactnativecommunity.webview.RNCWebViewPackage;
import com.reactnativelauncharguments.LaunchArgumentsPackage;
import com.zoontek.rnpermissions.RNPermissionsPackage;

import java.util.Arrays;
import java.util.List;
Expand All @@ -24,7 +25,8 @@ public static List<ReactPackage> getManualLinkPackages() {
new AsyncStoragePackage(),
new ReactCheckBoxPackage(),
new RNDateTimePickerPackage(),
new LaunchArgumentsPackage()
new LaunchArgumentsPackage(),
new RNPermissionsPackage()
);
}
}
3 changes: 3 additions & 0 deletions detox/test/android/settings.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,6 @@ project(':react-native-datetimepicker').projectDir = new File(rootProject.projec

include ':react-native-launcharguments'
project(':react-native-launcharguments').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-launch-arguments/android')

include ':react-native-permissions'
project(':react-native-permissions').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-permissions/android')
Loading

0 comments on commit 688d24f

Please sign in to comment.