mobile-qachecklists

Push-уведомления: чек-лист тестирования из 10 секций

Push-уведомления — один из самых недотестируемых классов фич. На QA-чек-листе обычно «пришёл / не пришёл», а реальных кейсов десятки: permission states, разные состояния приложения, deep linking, D...

Push-уведомления — один из самых недотестируемых классов фич. На QA-чек-листе обычно «пришёл / не пришёл», а реальных кейсов десятки: permission states, разные состояния приложения, deep linking, Do Not Disturb, локализация, тихие push, атрибуция в аналитике. Если хоть один из них сломан — вы теряете retention и не узнаете об этом.

Почему push сложно тестировать

  • Цепочка из 4 систем: ваш бекенд → APNs / FCM → ОС девайса → ваше приложение. Любая может сломаться отдельно.
  • Состояние приложения меняет поведение: foreground / background / killed — три разных кодовых пути обработки.
  • OS-уровень фильтров: DND, Focus modes (iOS 15+), Notification channels (Android 8+), Low Power Mode — каждый может дропнуть пуш молча.
  • Trigger часто асинхронный: между bekend-trigger и реальным push на экране может быть несколько секунд до минут. Тест «упало через минуту» — не баг и не норма, без замера.

1. Permission states

Главное что надо понимать — статусов больше двух. На iOS как минимум 5: notDetermined, denied, authorized, provisional (тихие пуши без пермишена), ephemeral (App Clip). Все надо проверить.

  • Первый запуск: при cold start новый юзер видит prompt? Или мы сначала показываем soft-ask (свой объяснительный экран), а потом системный prompt? Apple предпочитает второе.
  • Отказ от permission: после Don't Allow приложение продолжает работать, ничего не падает. UI корректно отражает «notifications off».
  • Включение через Settings: юзер отказал → пошёл в Settings → включил → вернулся в приложение. Получит ли он push при следующем триггере? Часто нет — токен не зарегистрировался ретроактивно.
  • Provisional authorization (iOS 12+): пуши приходят молча в Notification Center, юзер потом решает разрешить «громко» или не разрешить. Документация: Asking permission to use notifications.
  • Notification channels на Android 8+: каждый канал может быть индивидуально отключён. Тестируйте каждый. Документация: Notification channels.

2. Состояния приложения

Три ветки кода, которые разработчики часто пишут раз и забывают. Их надо протестировать раздельно.

  • Foreground: приложение открыто, юзер играет. На iOS пуш по умолчанию не показывается — приложение должно само решить (показать toast, in-app banner, или ничего). Тестируйте: пуш во время уровня → не ломает анимации, не воровствует фокус ввода.
  • Background: приложение свернули, экран блокирован или нет. Пуш показывается в Notification Center. Тап → приложение открывается на правильном экране (см. п.5 deep linking).
  • Killed (force-quit): юзер свайпнул приложение из task switcher. Пуш всё равно должен прийти. После тапа — приложение запускается с cold start, и должно знать, что было нажато (на iOS: launchOptions[.remoteNotification]). Проверьте, что deep-link отрабатывает после полного запуска.

3. Типы уведомлений

  • Alert / Banner: обычный визуальный пуш. Стандартный кейс.
  • Silent push (content-available): пуш без UI, чтобы приложение обновило данные в фоне. Тестировать очень сложно — без логирования вы не узнаете, пришёл он или нет. iOS жёстко троттлит silent push (несколько в час). Проверка через Console.app + фильтр по subsystem.
  • Time-sensitive (iOS 15+): пробивает Focus modes. Должны использоваться только для срочных уведомлений (Apple строго к этому относится — могут отклонить приложение).
  • Critical alert (iOS): пробивает Mute и DND. Требует специальный entitlement от Apple. В играх не используется почти никогда.
  • Provisional: тихие push, не требуют permission. Появляются в Notification Center, юзер решает.

4. Локализация и динамический контент

  • Локализованный текст: если push формируется на сервере — он должен учитывать locale юзера. Если на клиенте через loc-key (iOS) — ключи должны быть во всех Localizable.strings. Тестируйте на каждом языке.
  • Переменные в тексте: «У вас 3 жизни» — что если жизней 0, 1, 21? Плюрализация работает? (см. предыдущий пост про локализацию).
  • Эмодзи и спецсимволы: длинные имена, эмодзи, RTL-текст. Не обрезается, не ломает рендер.
  • Длина: iOS Lock Screen показывает ~2 строки, Notification Center раскрывает. Длинный текст — корректно укладывается в обе формы.

5. Deep linking из push

Самая частая проблема: тап на push открывает приложение на главном экране, а не на нужном.

  • Push «Скидка 50% на No-Ads» → тап → должен открыться Shop с подсвеченным офером. Не Home.
  • Push «Награда за level-up» → тап → должна открыться награда. Не Home.
  • Push приходит когда юзер уже в нужном экране → ничего не должно «прыгать». Только refresh данных.
  • Push с deep-link на экран, требующий авторизации → если юзер вышел → корректный flow «сначала логин, потом deep-link».
  • Push с deep-link на удалённый контент (event закончился, акция истекла) → понятный fallback на Home + информативное сообщение.

6. Фильтры на стороне ОС

Тестировать в каждом из этих режимов отдельно.

  • Do Not Disturb (DND): пуш приходит молча в Notification Center, не звенит, не светится. Это норма. Time-sensitive пробивают DND.
  • Focus modes (iOS 15+): юзер может разрешить уведомления только от Whitelisted apps. Если ваше приложение не в списке — пуш приходит в «summary» позже, не сразу. Тестируйте.
  • Low Power Mode: iOS режет background fetch, silent push приходят с задержкой или дропаются.
  • Doze mode (Android 6+): если девайс не двигался ~30 минут, неурочные push дропаются в maintenance windows. FCM-priority HIGH пробивает Doze.
  • Аэроплан-режим: накопившиеся пуши приходят пачкой, когда сеть вернулась. Должны корректно показаться, не дублироваться.

7. Аналитика и атрибуция

Без событий push — это слепая ставка маркетинга. Должны логироваться:

  • push_received — приложение получило (даже если юзер не открыл).
  • push_displayed — ОС показала пуш (важно: на iOS приложение этого не знает напрямую, нужны Notification Service Extensions для измерения).
  • push_opened — юзер тапнул и приложение открылось.
  • push_dismissed — юзер свайпнул не открывая (Android только).

Проверка: пуш с unique campaign_id → у себя в analytics видите 4 события с тем же campaign_id, attribution к follow-up действиям (покупке, level-up) сохраняется в течение N часов.

8. Edge cases из практики

  • Юзер сменил язык OS после регистрации токена — пуши приходят на старом языке, потому что сервер хранит локаль с момента регистрации.
  • Юзер сменил часовой пояс — пуш с relative time «через 1 час» приходит не вовремя. Сервер должен пересчитать.
  • Юзер удалил приложение и переустановил — старый push token инвалидирован. На сервере висит «зомби» в БД. APNs / FCM возвращают Unregistered — сервер должен это обработать и удалить токен.
  • Юзер дал permission, потом отозвал — токен у вас на сервере есть, но пуши не доходят. Без feedback вы будете слать в никуда.
  • Несколько устройств у одного юзера — пуш должен идти на все? Только на активное? Когда отзывать токен старого устройства? Это бизнес-решение, но QA-сценарий — обязателен.
  • Дубликаты — сервер случайно отправил один пуш дважды. Идемпотентность по apns-collapse-id (iOS) / collapse_key (FCM) — оба сводят одинаковые пуши в один.

9. iOS-специфика

  • APNs sandbox vs production: development-build бьёт в sandbox-APNs, App Store / TestFlight — в production. Они независимы — токен из sandbox не работает в production и наоборот. Самая частая «потеря пушей» при переходе с TestFlight на App Store.
  • Badge count: число на иконке. Управляется отдельно через badge в payload или клиентским API. QA: после открытия пуша счётчик корректно сбрасывается.
  • Notification Service Extension: для модификации пуша на лету (декодирование, скачивание медиа, аналитика). Если есть — отдельно тестировать.
  • Mutable content и медиа-аттачменты: пуш с картинкой / видео. На iOS — через NSE, лимит 5 MB.

10. Android-специфика

  • Notification channels: каждый канал имеет свою importance (HIGH = heads-up, LOW = silent). Юзер может отключить отдельные каналы в Settings. Тестировать каждый канал отдельно.
  • OEM-fragmentation: Xiaomi, Huawei, Samsung имеют свои системы аггресивной батареи. На Xiaomi приложение надо явно добавить в «Autostart whitelist», иначе пуши перестают приходить через несколько часов. Это не баг приложения — это OS. Но юзер не знает.
  • FCM priority: NORMAL дропается в Doze, HIGH пробивает. Используйте HIGH только для срочных — Google наказывает аппы за злоупотребление.
  • Background restrictions на Android 12+: можно вызвать сервис из push только в окне 10 секунд. Если ваш handler делает что-то долгое — упадёт.

QA-чек-лист на push-фичу

  • Permission prompt появляется в нужный момент, отказ не ломает приложение
  • Push приходит в foreground / background / killed — все три ветки
  • Тап в каждом состоянии открывает правильный экран (deep link)
  • Локализация: 2-3 «тяжёлых» языка, плюрализация работает
  • DND / Focus mode / Low Power: пуш приходит соответственно настройкам ОС
  • Notification channels на Android: каждый отключаем-включаем индивидуально
  • Badge count корректно меняется и сбрасывается
  • Аналитика 4 события: received / displayed / opened / dismissed
  • Attribution к follow-up действиям сохраняется N часов
  • Удаление и переустановка приложения — старый токен инвалидируется на сервере
  • Дубликаты сворачиваются через collapse-id
  • Edge: смена языка, часового пояса, multi-device

Тулзы для тестирования

  • APNs Tester / Pusher / NWPusher — десктоп-приложения для отправки тестовых push на iOS без сервера.
  • Firebase Console Cloud Messaging → Send test message — для FCM/Android, можно отправить пуш на конкретный токен.
  • Push Notification Tester — VS Code / JetBrains plugins для отправки прямо из IDE.
  • Charles / Proxyman — поймать запрос регистрации токена, посмотреть что ваш бекенд отправляет в APNs/FCM.
  • Console.app на Mac + USB-iPhone: фильтр subsystem:com.apple.cfnetwork покажет даже silent push.

Полезные ссылки