CalcSnippets
Mobile Development 4 min read

Test iOS Permission States With `simctl privacy` Instead of Manual Tapping

Automate supported simulator privacy grants and resets so permission tests begin from a known state. Confirm service names and app identity against the installed Xcode.

Make permission state part of the test setup

Camera, location, photos, and notification behavior depends on prior simulator decisions. A test run that inherits yesterday's permission state is not deterministic. simctl privacy can grant, revoke, or reset supported services for a simulator, which helps create known test preconditions.

Inspect the command help from the Xcode version used by the test runner:

xcrun simctl help privacy
xcrun simctl privacy booted reset all com.example.app

Service names and behavior can vary with platform support, so use the local help output as the authority. Target a simulator UDID rather than booted when the machine may run multiple devices.

Test both grant and denial paths

Granting a permission is not enough. A complete test should also verify first-request behavior, denial, limited access where applicable, and a later settings change. The app should handle denied access with a clear user path rather than repeatedly prompting.

Use the correct bundle identifier for the installed build. If the app ID differs by flavor, a privacy command against the production ID will not configure the staging app. Reset state between cases and assert the app's visible behavior, not merely the simulator database state.

Do not use simulator grants to claim that real-device permission behavior is fully tested. Physical devices can have different capabilities and OS prompts. Keep a small manual device check for the release path, while using deterministic simulator setup for regression coverage. This makes a permission bug reproducible without turning the test into a long sequence of UI taps.

Build permission tests as state transitions

For each service, define the initial state, the action that requests access, the result for grant, denial, limited access, and the behavior after a settings change. Reset before each case and target an explicit simulator UDID when more than one device is booted. A command that succeeds against booted can configure the wrong simulator in a busy CI runner.

Use the installed app's exact bundle identifier, including flavor suffixes. A privacy grant for a staging ID does not configure the production ID. Check the command help from the Xcode version used by CI because supported service names and options can vary. Treat simctl output as setup evidence, then assert the app's visible behavior as the real test.

Keep a physical-device test for capabilities that the simulator cannot represent accurately. Do not grant every permission globally just to make tests pass; that hides first-run behavior and can create a false release confidence. Reset or revoke after the suite so manual QA does not inherit an artificial state.

Build a permission matrix with fresh install, prior denial, limited access where supported, settings change, cancellation, and repeated request. Use the installed flavor's exact bundle ID and an explicit simulator UDID. Check local Xcode help for supported service names because commands vary across tool versions. Assert visible application behavior after setup; a successful simctl command alone does not prove the app handles the state correctly.

Build a permission matrix with fresh install, prior denial, limited access where supported, settings change, cancellation, and repeated request. Use the installed flavor's exact bundle ID and an explicit simulator UDID. Check local Xcode help for supported service names because commands can vary across tool versions. Assert visible application behavior after setup; a successful simctl command alone does not prove the app handles the state correctly.

Run the matrix on the product's supported OS versions and retain a physical-device check for hardware-specific behavior. The simulator is ideal for deterministic setup but cannot prove every real-device prompt. Record bundle ID, service, desired state, and observed UI state. Keep grant and denial cases separate so a previous authorization cannot make a later test pass accidentally.

Include cancellation, limited selection, a settings change while backgrounded, and repeated requests. The app should explain recovery without prompting in a loop. Tie the command syntax to the Xcode version used by CI and fail setup clearly when the requested service is unsupported.

Run the matrix on supported OS versions and retain a small physical-device check for hardware-specific behavior. The simulator is ideal for deterministic setup but cannot prove every real-device prompt. Record bundle ID, service, desired state, and observed UI state so a failure is reproducible rather than a vague permission complaint.

Keep reading

Related guides