Регрессионный контроль deep link для iOS на облачном Mac

Регрессионный контроль deep link для iOS на облачном Mac

После того как iOS-приложение начинает использовать deep link для возврата после входа, переходов на страницы акций и заказов, а также открытия уведомлений, даже один рефакторинг маршрутизации может привести к тому, что ссылка будет открывать главный экран или повторно добавлять ту же страницу в стек уже запущенного приложения. Ручная проверка нескольких ссылок подтверждает лишь их работоспособность в конкретный момент и не охватывает холодный запуск, активное состояние приложения, кодированные параметры и некорректные входные данные. Надёжнее зафиксировать окружение симулятора на облачном Mac и преобразовать итоговый маршрут каждой deep link в результат, доступный системе непрерывной интеграции.

Не собирайте URL по частям в разных местах тестового сценария. Сначала подготовьте реестр маршрутов с явно заданными входными данными, ожидаемым маршрутом и состоянием приложения.

Сценарий Входные данные Ожидаемый результат
Карточка товара linklab://product/42 product/42
Параметр поиска linklab://search?q=swift%20ui search?query=swift ui
Отсутствующий идентификатор linklab://product/ error/invalid-product
Неизвестный путь linklab://unknown error/not-found

Ожидаемые значения в реестре должны представлять собой каноническую форму после обработки маршрутизатором, а не текст кнопки в интерфейсе. Тогда изменения формулировок в UI не будут вызывать ложные срабатывания, а тест сможет сразу показать, где возникла проблема: в пути, параметрах или восстановлении состояния.

Контроль deep link проверяет, «попали ли входные данные в нужный бизнес-маршрут», а не просто «приняла ли система URL». Успешное завершение команды ещё не означает, что открылась правильная страница.

Для каждого маршрута следует подготовить как минимум два сценария: холодный запуск и уже открытое приложение. Если маршрут зависит от аутентификации, отдельно проверяйте состояния до и после входа. При этом состояние тестовой учётной записи должен явно задавать сценарий — наследовать его от предыдущего теста нельзя.

Добавьте наблюдаемый пробник в отладочную сборку

Командная строка позволяет открыть ссылку, но не даёт надёжно определить, какой экран в итоге показало приложение. В сборке DEBUG единая точка входа маршрутизатора может записывать нормализованный результат в каталог кэша. В релизную сборку эта логика попадать не должна; также нельзя сохранять токены или полные строки запросов.

#if DEBUG
func recordResolvedRoute(_ route: String) {
    let payload: NSDictionary = [
        "route": route,
        "recordedAt": Date().timeIntervalSince1970
    ]
    let directory = FileManager.default.urls(
        for: .cachesDirectory,
        in: .userDomainMask
    )[0]
    let file = directory.appendingPathComponent("link-probe.plist")
    payload.write(to: file, atomically: true)
}
#endif

Пробник следует размещать после общего маршрутизатора, через который проходят URL Scheme, Universal Link и переходы из уведомлений. Если записывать результат отдельно в каждом callback жизненного цикла, тест может доказать лишь сам факт вызова callback, но не подтвердит, что параметры прошли проверку и переход на нужную бизнес-страницу состоялся.

Обеспечьте атомарность результатов

Перед каждым запуском удаляйте старый файл, а новый записывайте с атомарной заменой. Иначе сбой приложения или слишком раннее чтение могут привести к тому, что текущий тест примет результат предыдущего запуска за свой. Рекомендуется сохранять только ключ маршрута, тип ошибки и временную метку, предварительно обезличивая пользовательские данные.

Зафиксируйте симулятор и выполните матрицу маршрутов

В CI не следует использовать неоднозначное состояние booted как отправную точку. Сначала определите UDID по названию устройства и версии системы, убедитесь, что запущен только целевой симулятор, а затем дождитесь полной загрузки ОС. В следующем примере предполагается, что конвейер уже получил SIMULATOR_UDID.

set -euo pipefail

xcrun simctl shutdown all
xcrun simctl boot "$SIMULATOR_UDID"
xcrun simctl bootstatus "$SIMULATOR_UDID" -b

xcodebuild \
  -scheme LinkLab \
  -configuration Debug \
  -sdk iphonesimulator \
  -derivedDataPath .ci/DerivedData \
  build

APP_PATH=".ci/DerivedData/Build/Products/Debug-iphonesimulator/LinkLab.app"
xcrun simctl install "$SIMULATOR_UDID" "$APP_PATH"

После установки сначала получите путь к контейнеру данных, а затем для каждой ссылки удаляйте пробник, открывайте URL и опрашивайте результат. Не используйте фиксированный длительный sleep: при изменении скорости запуска приложения он либо будет напрасно замедлять тест, либо всё равно завершится до появления файла.

BUNDLE_ID="com.example.linklab"
DATA_DIR="$(xcrun simctl get_app_container \
  "$SIMULATOR_UDID" "$BUNDLE_ID" data)"
PROBE="$DATA_DIR/Library/Caches/link-probe.plist"

rm -f "$PROBE"
xcrun simctl openurl "$SIMULATOR_UDID" "linklab://product/42"

for attempt in 1 2 3 4 5 6 7 8 9 10; do
  test -f "$PROBE" && break
  sleep 1
done

test -f "$PROBE"
ACTUAL="$(/usr/libexec/PlistBuddy -c "Print :route" "$PROBE")"
test "$ACTUAL" = "product/42"

В сценарии для активного приложения отправляйте следующий URL после первого открытия. Для проверки холодного запуска предварительно выполняйте simctl terminate. Для каждого неуспешного сценария сохраняйте входные данные, ожидаемое и фактическое значения, а также журнал теста, но не архивируйте весь контейнер приложения, если он может содержать секретные данные.

URL Scheme позволяет стабильно тестировать маршрутизатор, но не заменяет сквозную проверку Universal Link. Работа Universal Link дополнительно зависит от разрешений связанных доменов, файла сайта по HTTPS, типа содержимого, идентификатора приложения и системного кэша.

На первом уровне при каждом коммите напрямую вызывайте единый маршрутизатор, чтобы быстро проверять нормализацию путей, процентное кодирование, повторяющиеся параметры, пустые значения и неизвестные маршруты. На втором уровне открывайте реальные HTTPS-ссылки в контролируемом тестовом домене и после переустановки приложения проверяйте холодный запуск и переход в уже активное приложение. Изменение файла ассоциации сайта обязательно должно запускать второй уровень — одних модульных тестов недостаточно.

Распространённая ошибка — сразу после изменения файла ассоциации несколько раз открыть ссылку и принять результат из старого кэша за результат новой конфигурации. При диагностике фиксируйте номер сборки приложения, версию системы, порядок установки и заголовки ответа тестового домена. Сначала докажите, что система передала ссылку приложению, и только затем проверяйте бизнес-маршрут.

Включите свидетельства сбоя и очистку в контроль слияния

Надёжный контроль должен не только возвращать ненулевой код завершения, но и помогать сопровождающему быстро определить уровень сбоя. Сводка ошибки должна как минимум содержать название сценария, состояние приложения, тип входных данных, ожидаемый и фактический маршруты, а также информацию о том, был ли создан файл пробника. Если файла нет, сначала проверьте установку, точки входа жизненного цикла и объявления ссылок. Если файл существует, но маршрут неверен, исследуйте парсер и состояние навигации.

Контрольный список перед слиянием может оставаться коротким:

  • модель симулятора и версия системы зафиксированы конвейером;
  • холодный запуск и активное состояние приложения проверяются отдельно;
  • перед каждым сценарием очищается процесс приложения или состояние пробника;
  • для специальных символов, пустых параметров и неизвестных путей предусмотрены негативные сценарии;
  • для Universal Link выполняется отдельная проверка по HTTPS;
  • артефакты сбоев не содержат учётных данных, токенов и пользовательской информации;
  • после завершения задания симулятор выключается, а временный каталог сборки удаляется.

В конце выполните xcrun simctl shutdown "$SIMULATOR_UDID" и поручите политике очистки рабочей области удалить .ci/DerivedData. Только сочетание контракта маршрутов, наблюдаемого пробника и изоляции состояния превращает проверку deep link из теста «ссылка открывается» в инженерный контроль, способный блокировать регрессии навигации.

Часто задаваемые вопросы

Достаточно ли simctl openurl для полной проверки Universal Link?

Нет. Команда проверяет поведение приложения, но отдельно нужны HTTPS-файл ассоциации, подписанные разрешения, корректный TLS и учёт системного кеша.

Зачем проверять холодный запуск и уже открытое приложение?

Эти состояния могут использовать разные callbacks жизненного цикла. Оба теста выявляют потерянный начальный маршрут и повторную навигацию.

ZoomMini Mac в облаке

Выберите выделенный физический узел для сборки, тестирования и экспериментов

Две конфигурации M4 доступны в узлах Сингапура, Токио, Сеула и Гонконга. Ориентируйтесь на результат, который система вернёт при оформлении заказа.

Выбрать модель и заказать