Один и тот же набор UI-тестов iOS может без ошибок проходить на машине разработчика, но периодически зависать на диалоге разрешений после переноса на облачный Mac. Обычно причина не в случайном сбое бизнес-логики, а в том, что симулятор сохранил состояние разрешений TCC от предыдущего задания. Если кто-то уже нажал «Разрешить», последующие тесты не увидят первоначальный запрос. Если было выбрано «Не разрешать», сценарии, которым нужны контакты или фотографии, сразу перейдут в ветку отказа. Стабильность достигается не более частыми повторами, а явным заданием исходного состояния разрешений перед каждым тестом.
Рассматривайте состояние разрешений как входные данные теста
Тесты разрешений должны охватывать как минимум три состояния: решение ещё не принято, доступ разрешён и доступ запрещён. Каждому из них соответствует отдельный пользовательский сценарий, поэтому нельзя сводить их к одному шагу «переустановить приложение».
| Целевое состояние | Действие simctl | Ожидаемое поведение |
|---|---|---|
| Решение не принято | reset |
Приложение запрашивает доступ, система показывает диалог разрешения |
| Доступ разрешён | grant |
Функция продолжает работу без повторного запроса |
| Доступ запрещён | revoke |
Приложение показывает описание ограниченного режима или предлагает перейти в настройки |
Сначала убедитесь, что среда выполнения поддерживает нужную службу. Набор управляемых служб может различаться между версиями системы, поэтому не следует указывать имя службы по памяти и сразу отправлять изменения:
xcrun simctl privacy help
xcrun simctl list devices available
К распространённым службам относятся contacts, photos, photos-add, location, microphone и calendar. Тестовый скрипт должен ориентироваться на справку той версии simctl, которая входит в текущую поставку Xcode.
Состояние разрешений относится к устройству симулятора, а не к репозиторию кода. Очистка каталога сборки не удаляет данные TCC, а удаление и повторная установка приложения не должны считаться надёжным способом сброса разрешений.
Подготовьте отдельный симулятор и приложение
В CI не следует использовать booted, позволяя ему указывать на произвольное запущенное устройство. Параллельные задания могут выбрать один и тот же симулятор, из-за чего установка, запуск и изменение разрешений будут перезаписывать результаты друг друга. Надёжнее назначить заданию конкретный UDID и передавать его скрипту как параметр.
Чтобы grant или revoke применялись к реальному Bundle Identifier, приложение необходимо сначала установить. Не следует выводить Bundle Identifier из имени Scheme: его можно прочитать непосредственно из результата сборки.
APP_PATH="$1"
UDID="$2"
BUNDLE_ID=$(/usr/libexec/PlistBuddy -c "Print :CFBundleIdentifier" \
"$APP_PATH/Info.plist")
xcrun simctl boot "$UDID" 2>/dev/null || :
xcrun simctl bootstatus "$UDID" -b
xcrun simctl install "$UDID" "$APP_PATH"
printf '%s
' "$BUNDLE_ID"
Если тестируется расширение, у него есть собственный Bundle Identifier. Прочитайте Info.plist расширения отдельно и не используйте идентификатор основного приложения во всех командах управления разрешениями.
Зафиксируйте три исходных состояния с помощью скрипта
Переключение состояний удобнее сосредоточить в одной функции, чем распределять по конфигурации конвейера: такой код проще проверять. Перед изменением разрешений завершите приложение, чтобы процесс не удерживал прежнее состояние и не показывал диалог запроса.
set -euo pipefail
UDID="$1"
BUNDLE_ID="$2"
SERVICE="$3"
STATE="$4"
xcrun simctl terminate "$UDID" "$BUNDLE_ID" 2>/dev/null || :
case "$STATE" in
undecided)
xcrun simctl privacy "$UDID" reset "$SERVICE" "$BUNDLE_ID"
;;
authorized)
xcrun simctl privacy "$UDID" grant "$SERVICE" "$BUNDLE_ID"
;;
denied)
xcrun simctl privacy "$UDID" revoke "$SERVICE" "$BUNDLE_ID"
;;
*)
printf 'Unknown permission state: %s
' "$STATE" >&2
exit 64
;;
esac
После выполнения скрипта запускайте приложение или тесты. Например, для сценария с разрешённым доступом к контактам можно сначала вызвать скрипт, а затем выполнить только соответствующий тестовый класс:
./set-permission.sh "$UDID" "$BUNDLE_ID" contacts authorized
xcodebuild test \
-scheme PermissionTests \
-destination "platform=iOS Simulator,id=$UDID" \
-only-testing:PermissionUITests/ContactsAuthorizedTests \
-resultBundlePath Artifacts/ContactsAuthorized.xcresult
reset возвращает состояние «решение не принято», а не устанавливает отказ. Если бизнес-логика обрабатывает эти два состояния одинаково, тест должен явно выявить эту проблему проектирования.
Разделяйте тестирование системного диалога и бизнес-веток
Диалог разрешения относится к системному интерфейсу, поэтому поиск его кнопок зависит от языка, версии среды выполнения и порядка запросов. Не стоит заставлять все тесты разрешений взаимодействовать с этим диалогом. Разумнее разделить проверки по уровням: большинство тестов должны использовать grant и revoke для проверки бизнес-веток, а первоначальному запросу достаточно посвятить несколько отдельных сценариев.
Сценарий первоначального запроса
Сначала выполните reset, затем запустите приложение и инициируйте запрос разрешения. Тест должен проверять только появление диалога и переход приложения на ожидаемый экран после выбора пользователя. Если требуется взаимодействие с системным интерфейсом, используйте отдельный объект системного приложения и не полагайтесь на фиксированные координаты.
Сценарии разрешённого и запрещённого доступа
В этих двух сценариях диалог появляться не должен. При разрешённом доступе проверяйте, что функция корректно читает данные. При запрещённом — что приложение не запрашивает разрешение по кругу и не остаётся в состоянии бесконечной загрузки. Если предусмотрен переход в настройки, в автоматизированном задании следует проверять только кнопку и пояснение, не изменяя системные настройки на практике.
Для геолокации также необходимо различать такие бизнес-сценарии, как доступ во время использования приложения и постоянный доступ. simctl privacy позволяет подготовить базовое состояние, но не воспроизводит все особенности реального устройства, поэтому соответствующие выводы нужно дополнительно проверять на физическом устройстве.
Изоляция, сбор данных и диагностика сбоев
После завершения каждого тестового класса можно выполнить reset для соответствующей службы, но гораздо важнее, чтобы следующий тест самостоятельно создавал нужное состояние и не зависел от успешной очистки предыдущим тестом. При параллельном выполнении один симулятор должен принадлежать только одному заданию. Для конкурентного запуска создавайте несколько устройств и не используйте общий UDID.
При сбое сохраняйте как минимум .xcresult, журналы тестов, системные журналы симулятора и текущий список устройств. Если ожидаемое состояние не применилось, проверяйте следующие пункты по порядку:
- Получен ли Bundle Identifier из фактически установленного приложения или расширения.
- Поддерживается ли имя службы текущей средой выполнения.
- Было ли приложение завершено до изменения состояния.
- Совпадает ли целевой UDID с устройством, которое использует
xcodebuild. - Не работает ли с тем же симулятором другое параллельное задание.
- Не кэширует ли тестовый код прежний результат авторизации.
Не используйте полное стирание симулятора как стандартный способ исправления. Это заметно увеличивает продолжительность задания и может скрыть недостатки управления состоянием. Пересоздавайте устройство только при устойчивом и необъяснимом состоянии системы, сохранив перед этим имеющиеся журналы.
При выполнении таких заданий на облачных Mac от MiniRent модель и узел влияют только на размещение задания. Тестовый скрипт всё равно должен полностью описывать среду через UDID, Bundle Identifier, имя службы и целевое состояние, а доступные конфигурации следует проверять в консоли. Тогда один и тот же набор регрессионных тестов разрешений можно будет как воспроизводить в краткосрочных отладочных заданиях, так и стабильно выполнять в непрерывной интеграции.
Часто задаваемые вопросы
Может ли simctl privacy заменить все тесты системного запроса?
Нет. Команда хорошо подготавливает состояние, но текст настоящего диалога, его кнопки и возврат в приложение следует проверять отдельным небольшим набором UI-тестов.
Нужно ли очищать симулятор перед каждым тестом разрешений?
Обычно нет. Завершите приложение и выполните reset, grant или revoke для нужной службы и идентификатора пакета.
Запустите следующую задачу разработки или сборки на облачном Mac mini
Выберите одну из двух конфигураций M4, четырех сроков аренды и пяти доступных площадок. Фактический статус доступности отображается в консоли в реальном времени.