Тестирование диплинков: deep links, Universal Links, App Links и матрица состояний
Диплинки — это то, на чём держатся реклама, рефералы, шеринг и push: ссылка должна открыть нужный экран внутри приложения, а не главную и не браузер. Звучит просто, но именно тут прячется матрица состояний, из-за которой «иногда работает, иногда нет». Разберём, как тестировать диплинки по-взрослому.
Три типа ссылок — и что у каждого ломается
- Кастомная схема (
myapp://route): простая, но если приложение не установлено — ссылка никуда не ведёт (ошибка/пусто). Не годится как единственный механизм. - iOS Universal Links (обычный
https://): открывают приложение, если установлено, иначе — сайт. Требуют файлаapple-app-site-association(AASA) на домене. - Android App Links (тоже
https://): аналог для Android, требуютassetlinks.jsonи верификации. Без верификации система покажет «диалог выбора» или откроет браузер.
Матрица состояний — главный источник багов
Один и тот же диплинк должен корректно отработать во всех состояниях приложения:
- Приложение запущено (foreground) → переход на нужный экран без перезагрузки.
- В фоне (background) → возврат и навигация в нужное место.
- Убито (killed) / холодный старт → запуск, и после инициализации — переход (частый баг: диплинк теряется, открывается главная).
- Не установлено → стор или веб-фоллбэк (см. deferred ниже).
Минимальный тест-набор для каждого диплинка = 4 состояния × (валидный / битый линк). Большинство багов — именно в killed + cold start.
Deferred deep linking
Сценарий: пользователь кликнул ссылку, приложения нет → ушёл в стор → после установки должен попасть на тот экран, который был в ссылке (товар, промо, приглашение в группу). Это deferred deep linking — навигация «доезжает» после установки. Проверяем, что:
- Целевой экран открывается при первом запуске после установки по ссылке.
- Атрибуция корректна: трекер (Adjust/AppsFlyer) понял, какая кампания/реферал привели — что такое deferred deep linking.
- Если данные не доехали — вменяемый дефолтный экран, не пустота и не краш.
Fallback и битые ссылки
- Приложения нет → открыть стор (правильный для платформы) или веб-версию страницы.
- Устаревший/битый линк (товар удалён, акция закончилась) → понятный экран («не найдено»/главная раздела), не белый экран и не вылет.
- Неизвестный путь в ссылке → graceful-обработка, дефолтный роутинг.
Безопасность: не доверяй ссылке слепо
Диплинк — это ввод от пользователя, которым управляет кто угодно. Проверяем:
- Параметры из ссылки не ведут в приватные экраны в обход авторизации (открыл
myapp://account/settingsбез логина). - Нет открытого редиректа:
?next=https://evil.comне уводит пользователя/токен наружу. - Инъекции в параметры (id, search) экранируются, не ломают экран и не лезут в запросы.
- Чувствительные действия (оплата, смена данных) не выполняются «по ссылке» без подтверждения.
Верификация домена — почему «открывается в браузере»
Классическая жалоба: «Universal Link открывает сайт, а не приложение». Причина почти всегда — файл связи домена:
- iOS:
apple-app-site-association(AASA) доступен поhttps://домен/.well-known/apple-app-site-association, отдаётся как JSON, без редиректов, с правильным appID. См. Associated Domains и Universal Links. - Android:
assetlinks.jsonв/.well-known/, верификация App Links прошла (verify Android App Links, App Links). - Важно: Universal Link надо тапнуть (из Заметок/мессенджера), а не вставить в адресную строку Safari — там он откроется как обычный сайт. Это не баг, это поведение системы.
Атрибуция и аналитика
Маркетинг платит за переходы, поэтому параметры кампании (utm/referral/ad id) должны долетать в трекер ровно один раз и привязываться к правильному источнику. Проверяем: дубли событий, потерю параметров при cold start, корректность при deferred-сценарии.
Инструменты
- Android:
adb shell am start -W -a android.intent.action.VIEW -d "<url>" <package>— открыть диплинк на устройстве/эмуляторе. - iOS-симулятор:
xcrun simctl openurl booted "<url>". - Проверка AASA/assetlinks и редиректов — через перехват HTTP (Proxyman/Charles): что реально отдаёт домен.
- Реальный тап: ссылка в Заметках/чате (не в адресной строке), на чистом устройстве для deferred.
Чек-лист по диплинкам (12 пунктов)
- Каждый диплинк проверен в 4 состояниях: foreground / background / killed / не установлено.
- Killed + cold start ведёт на нужный экран (не на главную).
- Deferred: после установки по ссылке открывается целевой экран.
- Атрибуция кампании/реферала корректна и без дублей.
- Fallback при отсутствии приложения → стор/веб.
- Битый/устаревший линк → вменяемый экран, не краш.
- Кастомная схема не единственный механизм (есть UL/App Links).
- AASA доступен, JSON, без редиректа, правильный appID (iOS).
- assetlinks.json на месте, App Links верифицированы (Android).
- Ссылка не открывает приватные экраны в обход авторизации.
- Нет открытого редиректа и инъекций через параметры.
- Проверено на реальном тапе (Заметки/чат), не только из адресной строки.
Итог
Диплинк кажется «просто ссылкой», но его качество — это матрица состояний × фоллбэки × верификация домена × безопасность × атрибуция. Большинство багов живут в killed + cold start и в deferred, а «открывается в браузере» — почти всегда непроверенный AASA/assetlinks. Системный чек-лист по состояниям ловит это до того, как сломается рекламная кампания.