Глава 1.2: Архитектура: Нативный контейнер + Веб-движок

Разбираем «слоёный пирог» приложения и учимся локализовать баги

Глава 1.2 • Часть 1 из 8

Архитектура WebView-приложения

Понимание архитектуры — ключ к быстрой локализации багов. Без этого знания тестировщик не сможет правильно завести баг-репорт.

1 Слоёная архитектура приложения

Любое гибридное приложение с WebView можно представить как четырёхслойный пирог. Каждый слой отвечает за свою задачу, и баги чаще всего возникают на стыке этих слоёв.

Слой 4
Веб-контент (Frontend)
HTML + CSS + JavaScript
Слой 3
Веб-движок (Рендерер)
Blink (Android) / WebKit (iOS)
Слой 2
WebView-компонент
Мост между нативкой и вебом
Слой 1
Нативная обёртка
Swift / Kotlin

Почему это важно понимать: Когда пользователь жалуется, что «кнопка не работает», баг может быть в любом из четырёх слоёв. Задача QA — определить, в каком именно, и завести баг на правильную команду.

🎯 Главное правило

Баг в WebView — это почти всегда баг на стыке слоёв. Чистые веб-баги (кривая вёрстка) и чистые нативные баги (краш при открытии) встречаются реже. Основная масса проблем — в коммуникации между слоями.

2 Слой 1: Нативная обёртка (Swift / Kotlin)

Это «фундамент» приложения. Нативный код отвечает за всё, что не связано напрямую с отображением веб-контента.

Что делает нативный слой:

  • Инициализация WebView: Создаёт компонент, настраивает его параметры (разрешён ли JS, включён ли кэш, какие домены разрешены).
  • Жизненный цикл: Обрабатывает события приложения (foreground/background, поворот экрана, сворачивание).
  • Доступ к железу: Камера, геолокация, файловая система, микрофон — всё это нативные API.
  • Навигация: Нативные кнопки «Назад», «Закрыть», таб-бары, хедеры.
  • Сетевые настройки: Прокси, сертификаты, обработка SSL-ошибок.
  • Безопасность: Хранение токенов, управление cookies, очистка данных.
Пример: Инициализация WebView на Android (Kotlin)
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")
Пример: Инициализация WKWebView на iOS (Swift)
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 Bridge на Android (Kotlin)
// Нативный метод, доступный из JS
class WebAppInterface {
    @JavascriptInterface
    fun showToast(message: String) {
        Toast.makeText(context, message, Toast.LENGTH_SHORT).show()
    }
}

// Регистрация моста
webView.addJavascriptInterface(WebAppInterface(), "Android")

// Вызов из JavaScript:
// Android.showToast("Привет из веба!");
Пример: JS Bridge на iOS (Swift)
// Регистрация обработчика
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-потока при длительных нативных операциях.
⚠️ Частая ошибка QA

Не путайте WebView-компонент (слой 2) и веб-движок (слой 3). Компонент — это «обёртка» от Google/Apple, движок — это «движок» под капотом. Баги компонента — это баги API, баги движка — это баги рендеринга.

4 Слой 3: Веб-движок (Рендерер)

Это «сердце» WebView. Веб-движок непосредственно отрисовывает HTML/CSS и выполняет JavaScript. Именно от него зависит, как будет выглядеть страница и как быстро она загрузится.

Два основных движка:

  • Blink (Chromium) — используется в Android WebView. Тот же движок, что в Google Chrome.
  • WebKit — используется в iOS WKWebView. Тот же движок, что в Safari.

Как работает движок (упрощённо):

  1. Парсинг HTML: Движок читает HTML и строит DOM-дерево (Document Object Model).
  2. Парсинг CSS: Читает CSS и строит CSSOM-дерево (CSS Object Model).
  3. Построение Render Tree: Объединяет DOM и CSSOM в дерево рендеринга (только видимые элементы).
  4. Layout (Reflow): Вычисляет размеры и позиции всех элементов.
  5. Paint: Рисует пиксели на экране.
  6. Composite: Объединяет слои (например, для анимаций) в финальное изображение.
🎯 Почему это важно для QA

Если кнопка «не на том месте» или «не того цвета» — это баг рендеринга (слой 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, таймауты.
Пример: Базовая структура HTML для WebView
<!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 Локализация багов по слоям

Это самая важная часть главы. Здесь мы соберём всё воедино и научимся быстро определять, в каком слое живёт баг.

Алгоритм локализации:

  1. Воспроизведите баг и зафиксируйте шаги.
  2. Откройте DevTools (Chrome DevTools для Android, Safari Inspector для iOS).
  3. Проверьте консоль JavaScript: Есть ли ошибки? Если да — баг в слое 4 (веб-контент).
  4. Проверьте вкладку Network: Все ли ресурсы загрузились? Нет ли CORS-ошибок? Если ресурсы не грузятся — баг в слое 4 или 6 (сеть).
  5. Проверьте DOM-дерево: Элемент существует? Правильные ли у него стили? Если элемент есть, но не виден — баг в слое 3 (рендеринг) или 4 (CSS).
  6. Проверьте логи нативного приложения: Есть ли краши? Ошибки инициализации WebView? Если да — баг в слое 1 (нативная обёртка).
  7. Проверьте 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 Проверка обработки жизненного цикла в нативке
⚠️ Золотое правило QA

Всегда проверяйте баг в обычном браузере! Если он воспроизводится в Chrome/Safari — это 100% баг веб-контента (слой 4). Если не воспроизводится — баг в нативной обёртке, WebView-компоненте или движке (слои 1-3).

Пример из практики:

Баг: «На iOS кнопка "Оплатить" не работает, на Android всё ок».

Действия QA:

  1. Открываем страницу в Safari на iPhone → кнопка работает. Вывод: баг не в слое 4.
  2. Открываем Safari Web Inspector → консоль чистая, ошибок нет. Вывод: JS работает.
  3. Проверяем JS Bridge → вызов window.webkit.messageHandlers.pay.postMessage() не срабатывает.
  4. Смотрим нативный код → обработчик pay зарегистрирован только для Android.
  5. Итог: Баг в слое 1 (нативная обёртка iOS) — не зарегистрирован JS Bridge.
💡 Шаблон баг-репорта для WebView

Всегда указывайте в баг-репорте:

  • Платформа: Android / iOS
  • Версия ОС: Например, iOS 16.3
  • Версия WebView: (для Android — версия Android System WebView)
  • Предполагаемый слой: Слой 1 / 2 / 3 / 4
  • Логи: Приложите скриншоты консоли DevTools и нативных логов
  • Воспроизводится в браузере: Да / Нет