Архитектура WebView-приложения
Понимание архитектуры — ключ к быстрой локализации багов. Без этого знания тестировщик не сможет правильно завести баг-репорт.
1 Слоёная архитектура приложения
Любое гибридное приложение с WebView можно представить как четырёхслойный пирог. Каждый слой отвечает за свою задачу, и баги чаще всего возникают на стыке этих слоёв.
Почему это важно понимать: Когда пользователь жалуется, что «кнопка не работает», баг может быть в любом из четырёх слоёв. Задача QA — определить, в каком именно, и завести баг на правильную команду.
Баг в WebView — это почти всегда баг на стыке слоёв. Чистые веб-баги (кривая вёрстка) и чистые нативные баги (краш при открытии) встречаются реже. Основная масса проблем — в коммуникации между слоями.
2 Слой 1: Нативная обёртка (Swift / Kotlin)
Это «фундамент» приложения. Нативный код отвечает за всё, что не связано напрямую с отображением веб-контента.
Что делает нативный слой:
- Инициализация WebView: Создаёт компонент, настраивает его параметры (разрешён ли JS, включён ли кэш, какие домены разрешены).
- Жизненный цикл: Обрабатывает события приложения (foreground/background, поворот экрана, сворачивание).
- Доступ к железу: Камера, геолокация, файловая система, микрофон — всё это нативные API.
- Навигация: Нативные кнопки «Назад», «Закрыть», таб-бары, хедеры.
- Сетевые настройки: Прокси, сертификаты, обработка SSL-ошибок.
- Безопасность: Хранение токенов, управление cookies, очистка данных.
val webView = findViewById<WebView>(R.id.webview)
webView.settings.apply {
javaScriptEnabled = true
domStorageEnabled = true
allowFileAccess = false
mixedContentMode = WebSettings.MIXED_CONTENT_NEVER_ALLOW
}
webView.loadUrl("https://example.com")
let config = WKWebViewConfiguration()
config.allowsInlineMediaPlayback = true
config.preferences.javaScriptEnabled = true
let webView = WKWebView(frame: view.bounds, configuration: config)
view.addSubview(webView)
let url = URL(string: "https://example.com")!
webView.load(URLRequest(url: url))
Типичные баги нативного слоя:
- WebView не создаётся на старых версиях ОС.
- Не обрабатывается поворот экрана — контент обрезается.
- При сворачивании приложения WebView «забывает» состояние.
- Нативная кнопка «Назад» закрывает экран, а не листает историю WebView.
- Не запрашивается разрешение на доступ к камере перед вызовом из JS.
Используйте Android Studio и Xcode для запуска приложения в режиме отладки. Смотрите логи (Logcat / Console) — там видны ошибки инициализации WebView, проблемы с разрешениями, краши нативного кода.
3 Слой 2: WebView-компонент (Мост)
Это «переводчик» между нативным миром и веб-миром. WebView-компонент не рендерит контент сам — он передаёт его веб-движку и обеспечивает связь между слоями.
Что делает WebView-компонент:
- Загрузка URL: Принимает URL от нативного кода и передаёт его веб-движку.
- JS Bridge: Обеспечивает двустороннюю связь между JS и нативным кодом.
- Обработка событий: Перехватывает клики, скролл, жесты и передаёт их в DOM.
- Управление кэшем: Решает, что кэшировать, а что загружать заново.
- Обработка ошибок: Перехватывает ошибки загрузки (404, 500, нет сети) и передаёт их нативному коду.
JS Bridge — сердце взаимодействия:
Это механизм, который позволяет JavaScript вызывать нативные методы и наоборот. Без JS Bridge веб-страница была бы «изолирована» от приложения.
// Нативный метод, доступный из JS
class WebAppInterface {
@JavascriptInterface
fun showToast(message: String) {
Toast.makeText(context, message, Toast.LENGTH_SHORT).show()
}
}
// Регистрация моста
webView.addJavascriptInterface(WebAppInterface(), "Android")
// Вызов из JavaScript:
// Android.showToast("Привет из веба!");
// Регистрация обработчика
webView.configuration.userContentController.add(
self,
name: "showToast"
)
// Вызов из JavaScript:
// window.webkit.messageHandlers.showToast.postMessage("Привет!");
// Обработка в Swift
func userContentController(
_ userContentController: WKUserContentController,
didReceive message: WKScriptMessage
) {
if message.name == "showToast" {
if let body = message.body as? String {
showToast(body)
}
}
}
Типичные баги WebView-компонента:
- JS Bridge не работает — нативные методы не вызываются из JS.
- Потеря сообщений при быстром вызове (race condition).
- Неправильная обработка типов данных (JS передаёт строку, нативка ждёт число).
- Утечки памяти из-за неосвобождённых обработчиков JS Bridge.
- Блокировка UI-потока при длительных нативных операциях.
Не путайте WebView-компонент (слой 2) и веб-движок (слой 3). Компонент — это «обёртка» от Google/Apple, движок — это «движок» под капотом. Баги компонента — это баги API, баги движка — это баги рендеринга.
4 Слой 3: Веб-движок (Рендерер)
Это «сердце» WebView. Веб-движок непосредственно отрисовывает HTML/CSS и выполняет JavaScript. Именно от него зависит, как будет выглядеть страница и как быстро она загрузится.
Два основных движка:
- Blink (Chromium) — используется в Android WebView. Тот же движок, что в Google Chrome.
- WebKit — используется в iOS WKWebView. Тот же движок, что в Safari.
Как работает движок (упрощённо):
- Парсинг HTML: Движок читает HTML и строит DOM-дерево (Document Object Model).
- Парсинг CSS: Читает CSS и строит CSSOM-дерево (CSS Object Model).
- Построение Render Tree: Объединяет DOM и CSSOM в дерево рендеринга (только видимые элементы).
- Layout (Reflow): Вычисляет размеры и позиции всех элементов.
- Paint: Рисует пиксели на экране.
- Composite: Объединяет слои (например, для анимаций) в финальное изображение.
Если кнопка «не на том месте» или «не того цвета» — это баг рендеринга (слой 3). Если кнопка «не работает при клике» — это, скорее всего, баг JavaScript (слой 4). Если кнопка «вообще не отображается» — возможно, баг нативной обёртки (слой 1), которая не передала URL.
Типичные баги веб-движка:
- Различия в рендеринге между Blink и WebKit (кроссбраузерность).
- Неподдерживаемые CSS-свойства (например,
-webkit-overflow-scrollingработает только в WebKit). - Проблемы с производительностью при сложных анимациях.
- Утечки памяти в JavaScript (движок не собирает мусор).
- Баги конкретных версий движка (например, краш в WebKit на iOS 14.5).
Используйте Chrome DevTools (для Android) и Safari Web Inspector (для iOS). Там видны DOM-дерево, CSS-стили, консоль JavaScript, сетевые запросы. Это ваши главные инструменты для поиска багов слоя 3 и 4.
5 Слой 4: Веб-контент (Frontend)
Это то, что видит пользователь: HTML-разметка, CSS-стили, JavaScript-логика. Веб-контент загружается с сервера (или из локальных файлов) и отрисовывается веб-движком.
Из чего состоит веб-контент:
- HTML: Структура страницы (кнопки, формы, текст, изображения).
- CSS: Внешний вид (цвета, шрифты, отступы, анимации).
- JavaScript: Логика (обработка кликов, валидация форм, запросы к API).
- Ресурсы: Изображения, шрифты, видео, иконки.
Особенности веб-контента в WebView:
- Адаптивность: Должен корректно отображаться на разных размерах экранов.
- Мета-теги:
<meta name="viewport">критически важен для корректного масштабирования. - JS Bridge: Веб-контент должен знать, как вызывать нативные методы (через
window.Androidилиwindow.webkit.messageHandlers). - Обработка ошибок: Должен корректно реагировать на отсутствие сети, ошибки API, таймауты.
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<title>Пример страницы</title>
<style>
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
padding: env(safe-area-inset-top) env(safe-area-inset-right)
env(safe-area-inset-bottom) env(safe-area-inset-left);
}
</style>
</head>
<body>
<h1>Привет из WebView!</h1>
<button onclick="callNative()">Вызвать нативный метод</button>
<script>
function callNative() {
// Android
if (window.Android) {
window.Android.showToast("Привет из веба!");
}
// iOS
else if (window.webkit && window.webkit.messageHandlers) {
window.webkit.messageHandlers.showToast.postMessage("Привет из веба!");
}
}
</script>
</body>
</html>
Типичные баги веб-контента:
- Кривая вёрстка на определённых разрешениях.
- Не работает JavaScript (ошибки в консоли).
- Не загружаются ресурсы (404, CORS-ошибки).
- Неправильная обработка кликов (событие не срабатывает).
- Проблемы с формами (не работает автозаполнение, не открывается клавиатура).
- Не учитывается Safe Area (контент залезает под «чёлку»).
Откройте страницу в обычном браузере (Chrome/Safari) и сравните с WebView. Если в браузере всё ок, а в WebView нет — баг в слоях 1-3. Если и в браузере криво — баг в слое 4 (веб-контент).
6 Локализация багов по слоям
Это самая важная часть главы. Здесь мы соберём всё воедино и научимся быстро определять, в каком слое живёт баг.
Алгоритм локализации:
- Воспроизведите баг и зафиксируйте шаги.
- Откройте DevTools (Chrome DevTools для Android, Safari Inspector для iOS).
- Проверьте консоль JavaScript: Есть ли ошибки? Если да — баг в слое 4 (веб-контент).
- Проверьте вкладку Network: Все ли ресурсы загрузились? Нет ли CORS-ошибок? Если ресурсы не грузятся — баг в слое 4 или 6 (сеть).
- Проверьте DOM-дерево: Элемент существует? Правильные ли у него стили? Если элемент есть, но не виден — баг в слое 3 (рендеринг) или 4 (CSS).
- Проверьте логи нативного приложения: Есть ли краши? Ошибки инициализации WebView? Если да — баг в слое 1 (нативная обёртка).
- Проверьте JS Bridge: Вызывается ли нативный метод? Передаются ли данные? Если нет — баг в слое 2 (WebView-компонент).
| Симптом | Возможный слой | Как проверить |
|---|---|---|
| Экран белый, ничего не загружается | Слой 1 или 6 | Логи нативки, проверка URL, наличие сети |
| Кнопка есть, но не реагирует на клик | Слой 4 | Консоль JS (ошибки?), вкладка Elements (есть ли onclick?) |
| Кнопка работает в браузере, но не в WebView | Слой 2 или 3 | Проверка JS Bridge, поддержка CSS/JS в движке |
| Контент кривой (не на том месте) | Слой 3 или 4 | DevTools → Elements → проверка стилей |
| Приложение крашится при открытии WebView | Слой 1 | Logcat / Console → поиск краш-репорта |
| Нативный метод не вызывается из JS | Слой 2 | Проверка регистрации JS Bridge, типов данных |
| Изображения не загружаются | Слой 4 или 6 | Network → проверка статус-кодов, CORS |
| WebView «забывает» состояние при сворачивании | Слой 1 | Проверка обработки жизненного цикла в нативке |
Всегда проверяйте баг в обычном браузере! Если он воспроизводится в Chrome/Safari — это 100% баг веб-контента (слой 4). Если не воспроизводится — баг в нативной обёртке, WebView-компоненте или движке (слои 1-3).
Пример из практики:
Баг: «На iOS кнопка "Оплатить" не работает, на Android всё ок».
Действия QA:
- Открываем страницу в Safari на iPhone → кнопка работает. Вывод: баг не в слое 4.
- Открываем Safari Web Inspector → консоль чистая, ошибок нет. Вывод: JS работает.
- Проверяем JS Bridge → вызов
window.webkit.messageHandlers.pay.postMessage()не срабатывает. - Смотрим нативный код → обработчик
payзарегистрирован только для Android. - Итог: Баг в слое 1 (нативная обёртка iOS) — не зарегистрирован JS Bridge.
Всегда указывайте в баг-репорте:
- Платформа: Android / iOS
- Версия ОС: Например, iOS 16.3
- Версия WebView: (для Android — версия Android System WebView)
- Предполагаемый слой: Слой 1 / 2 / 3 / 4
- Логи: Приложите скриншоты консоли DevTools и нативных логов
- Воспроизводится в браузере: Да / Нет