Инженерная практика

Как проверить обмен между WKWebView и нативным кодом

Как проверить обмен между WKWebView и нативным кодом

Один и тот же экран iOS исправно работает локально, но в удалённой сборке сообщение «готово» от веб-страницы иногда не приходит. Не стоит сразу винить сеть облачного Mac: если страница обменивается сообщениями с нативным кодом через мост WKWebView, причина может быть в порядке загрузки, полях сообщения или жизненном цикле обработчика. Локальный HTML-файл, хранящийся в репозитории, позволяет проверять контракт обмена в обоих направлениях без зависимости от внешней страницы.

Сначала зафиксируйте контракт моста

Задайте минимальный протокол: страница отправляет нативному коду {kind: "ready", version: 1}, а нативный код отправляет странице {kind: "sync", value: "ok"}. Первое сообщение передаётся через window.webkit.messageHandlers.bridge.postMessage, второе — через evaluateJavaScript. Тест должен отдельно подтверждать доставку сообщений и правильность типов полей. Одной проверки отображения страницы недостаточно.

Сохраните следующий код в файле BridgeFixture.html, доступном тестовому target, и убедитесь, что файл добавлен в его ресурсы:

<!doctype html>
<html lang="en">
<meta charset="utf-8">
<body>
<script>
window.bridgeReceive = function (message) {
  if (message.kind !== "sync" || typeof message.value !== "string") {
    throw new Error("Invalid native message");
  }
  document.body.dataset.received = message.value;
};
window.webkit.messageHandlers.bridge.postMessage({
  kind: "ready",
  version: 1
});
</script>
</body>
</html>

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

Проверьте оба направления в симуляторе

Тест создаёт WKUserContentController, регистрирует WKScriptMessageHandler под именем bridge и передаёт контроллер в WKWebViewConfiguration. Затем он читает HTML-файл из пакета тестов и вызывает loadHTMLString(_:baseURL:). При получении ready сначала проверьте, что message.body — словарь, а затем — что kind является строкой, version является числом и его значение равно 1. Незнакомое сообщение должно приводить к ошибке проверки.

Для отправки сообщения из нативного кода дождитесь, пока WKNavigationDelegate получит вызов webView(_:didFinish:). После этого закодируйте отправляемый словарь в JSON-текст с помощью JSONSerialization, подставьте его в window.bridgeReceive(...) и вызовите evaluateJavaScript. Не вставляйте пользовательский ввод непосредственно в строку скрипта: кавычки и переводы строк могут изменить смысл кода. Когда вызов завершится, прочитайте document.body.dataset.received и проверьте, что значение равно ok.

Возврат из loadHTMLString означает лишь начало загрузки. Даже если страница уже отправила ready, этот момент не заменяет событие завершения навигации. Заведите отдельное ожидание XCTest для каждого направления: так будет видно, на каком участке не пришёл ответ.

Предусмотрите также провал теста при ошибке загрузки или выполнения скрипта. Задайте для ожиданий явные тайм-ауты, а в сообщениях об ошибках различайте случаи «страница не завершила загрузку», «нативный код не получил сообщение» и «страница не приняла сообщение от нативного кода». Иначе CI оставит лишь общее сообщение о тайм-ауте.

Подключите тест к облачной сборке

Сначала проверьте в консоли облачного Mac ArmMesh доступные конфигурации. Выбрав узел, зафиксируйте версию Xcode и среду выполнения симулятора. Найдите доступные устройства командой xcrun simctl list devices available и внесите UUID выбранного симулятора в конфигурацию задания. Не полагайтесь на неточное совпадение имени устройства. В следующей команде замените проект, scheme и UUID фактическими значениями из репозитория:

xcodebuild test \
  -project Client.xcodeproj \
  -scheme Client \
  -destination 'platform=iOS Simulator,id=<SIMULATOR_UUID>' \
  -only-testing:ClientTests/BridgeContractTests \
  -resultBundlePath "$PWD/BridgeContract.xcresult"

Перед запуском убедитесь, что scheme является общедоступной, HTML-файл включён в ресурсы тестового target, а путь для результатов ещё не существует. xcodebuild не перезапишет существующий пакет результатов как пустой каталог. При сбое сохраните .xcresult и журнал сборки, а также запишите версию Xcode и UUID симулятора, чтобы повторить запуск в той же среде.

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

Учтите порядок событий и очистку

Если страница вызовет postMessage до регистрации обработчика, уже первое сообщение может потеряться. Поэтому сначала завершите настройку и регистрацию, а затем создавайте и загружайте WebView. Не используйте в тесте повторно контроллер со старым обработчиком. По окончании тестового случая вызовите removeScriptMessageHandler(forName: "bridge") и освободите ссылку WebView на делегат навигации.

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

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

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

Почему не использовать рабочий сайт как тестовую страницу?

Изменения сайта и состояние сети делают входные данные непостоянными. Зафиксируйте контракт локально, а интеграцию проверяйте отдельно.

Когда нативный код может вызвать функцию страницы?

После didFinish у WKNavigationDelegate. Возврат из loadHTMLString ещё не означает, что функция JavaScript готова.

Выделенный Apple Silicon

Подключите облачный Mac с фиксированной конфигурацией к своему рабочему процессу

Арендуйте выделенные физические узлы на день, неделю, месяц или квартал для сборок, автоматизации и задач инференса MLX на фиксированных аппаратных ресурсах.

Посмотреть доступные конфигурации