Когда команда запускает регрессионные тесты iOS на облачном Mac, приложение нередко последовательно открывает два веб-сеанса: сначала входит в рабочее пространство A, затем переключается на рабочее пространство B. Если B получает cookie из A, интерфейс может по-прежнему выглядеть исправным — проблема обнаружится только при смене пользователя. Поэтому вместо проверки «страница открывается» лучше сначала оформить границу между хранилищами данных сайтов как воспроизводимый тест.
Сначала определите границу изоляции
WKWebView выбирает хранилище данных сайтов через WKWebViewConfiguration.websiteDataStore. WKWebsiteDataStore.default() соответствует постоянному хранилищу; каждый вызов WKWebsiteDataStore.nonPersistent() создаёт отдельный временный экземпляр. Сначала мы проверим изоляцию cookie между двумя независимыми временными хранилищами. Успешный результат сам по себе не доказывает безопасность всего процесса входа.
Перед написанием теста найдите в коде приложения места создания веб-представлений и запишите, какое хранилище привязано к каждому из них. Если по требованиям несколько страниц одного пользователя должны разделять сеанс, им не следует по ошибке назначать независимые временные хранилища. Если после смены пользователя сеанс должен начинаться заново, убедитесь, что новое представление не ссылается на прежнее хранилище. Таблица с указанием, какие страницы разделяют данные, а какие изолированы, надёжнее попыток задним числом выяснить источник cookie.
WKProcessPoolздесь не является предметом проверки. Не делайте вывод о совместном использовании или изоляции данных сайтов только по тому, совпадают ли пулы процессов: проверяйте фактически привязанныйwebsiteDataStore.
Напишите тест хранилищ без зависимости от внешней сети
Добавьте WebKit и XCTest в тестовый target приложения. Приведённый ниже тест работает только с хранилищами cookie и не отправляет сетевых запросов. Ему не нужны ни тестовый сервер, ни быстрая загрузка страниц. test.invalid служит доменом для тестового cookie, а не адресом сервиса.
import WebKit
import XCTest
final class WebsiteDataIsolationTests: XCTestCase {
func testEphemeralStoresDoNotShareCookie() {
let storeA = WKWebsiteDataStore.nonPersistent()
let storeB = WKWebsiteDataStore.nonPersistent()
let done = expectation(description: "Both stores checked")
guard let cookie = HTTPCookie(properties: [
.domain: "test.invalid",
.path: "/",
.name: "isolation_probe",
.value: "only_a"
]) else {
XCTFail("Could not create probe cookie")
return
}
storeA.httpCookieStore.setCookie(cookie) {
storeA.httpCookieStore.getAllCookies { cookiesA in
XCTAssertTrue(cookiesA.contains {
$0.name == "isolation_probe" && $0.value == "only_a"
})
storeB.httpCookieStore.getAllCookies { cookiesB in
XCTAssertFalse(cookiesB.contains {
$0.name == "isolation_probe"
})
done.fulfill()
}
}
}
wait(for: [done], timeout: 5)
}
}
Сначала убедитесь, что A читает только что записанное значение, и лишь затем проверьте, что B не видит cookie с тем же именем. Порядок важен: если прочитать B до завершения записи, тест может «пройти», даже когда оба хранилища ошибочно разделяют данные. Тайм-аут означает, что цепочка обратных вызовов не завершилась. Это повод расследовать сбой, а не бесконечно увеличивать время ожидания.
Подключите хранилище к реальным страницам
Когда тест хранилищ пройдёт, проверьте, как приложение создаёт веб-представления. Конфигурацию необходимо задать до инициализации WKWebView: последующее изменение другого экземпляра конфигурации не заменит хранилище у уже созданного представления. В коде подготовки теста страницы можно явно передать нужный экземпляр:
let sessionStore = WKWebsiteDataStore.nonPersistent()
let configuration = WKWebViewConfiguration()
configuration.websiteDataStore = sessionStore
let webView = WKWebView(frame: .zero, configuration: configuration)
Затем интеграционный тест должен загрузить подконтрольную тестовую страницу, выполнить на ней вход, чтение состояния, смену пользователя и выход, после чего проверить запросы и отображение страницы. API хранилища позволяет доказать, что тестовый cookie не виден между двумя экземплярами. Но он сам по себе не доказывает ни завершение сеанса на сервере, ни отсутствие повторной передачи токена на страницу из нативного кода приложения. Если тесту нужны реальные запросы, используйте подконтрольный сервис, который поддерживает ваша команда, и фиксируйте сетевые сбои отдельно от результатов проверок сеанса.
Постоянное хранилище требует отдельного тестирования
Не считайте успешный тест с nonPersistent() подтверждением правильной работы выхода из учётной записи. Если приложение использует default(), тест выхода должен проверить, что соответствующие данные сайтов в постоянном хранилище обработаны по правилам продукта, а затем повторить проверку после создания нового веб-представления. Перед очисткой ограничьте набор удаляемых данных: не стирайте без разбора записи, которые ещё могут быть нужны другим страницам на устройстве, лишь ради успешного теста.
Обеспечьте воспроизводимость на облачном Mac
Запускайте один и тот же тестовый target на машине разработчика и в автоматизированных заданиях, сохраняя результаты каждого запуска отдельно. Перед запуском зафиксируйте требуемые проектом версии Xcode и ОС симулятора, а также конфигурацию тестирования; предполагать пригодность какой-либо конкретной версии системы здесь не требуется. Создавайте временные хранилища заново в каждом тестовом методе и используйте отдельное имя тестового cookie, чтобы разные тесты не заимствовали состояние друг у друга.
Если тест проходит отдельно, но падает при запуске всего набора, сначала проверьте три вещи: не стал ли он незаметно использовать default(); не получили ли два веб-представления один и тот же экземпляр хранилища; выполняются ли асинхронная запись, переходы между страницами и проверки в порядке, заданном обратными вызовами завершения. Не пытайтесь прежде всего скрыть проблему увеличением времени ожидания. Для последующего разбора сохраните имя упавшего теста, версии Xcode и симулятора, а также файл с результатами тестирования. Не выводите реальные значения сеанса в журнал.
Что проверить перед сдачей
| Проверка | Критерий прохождения |
|---|---|
| Привязка хранилища | websiteDataStore каждого веб-представления соответствует ожидаемому сеансу |
| Изоляция временных хранилищ | Тестовый cookie, записанный в A, не виден в независимом B |
| Порядок обратных вызовов | Сначала завершается запись, затем читается A и в последнюю очередь B |
| Проверка выхода | Постоянные данные и серверный сеанс проверяются отдельно по правилам продукта |
| Данные о сбое | Фиксируются окружение и результаты проверок, но не реальные учётные данные |
Эта проверка лучше всего подходит для тестирования границ: сначала она подтверждает, что локальные данные не переходят между временными хранилищами, а затем тесты на уровне страниц проверяют смену пользователя и выход. Записывайте результаты двух уровней отдельно — тогда при сбое будет понятно, где искать причину: в привязке хранилища, порядке асинхронных операций или логике сеансов самого приложения.
Часто задаваемые вопросы
Удаляет ли временное хранилище постоянные cookies?
Нет. nonPersistent() не очищает default(). Выход из приложения проверяйте отдельно для постоянного хранилища, которое оно использует.
Достаточно ли разных WKProcessPool для изоляции cookies?
Нет оснований считать это достаточным. Проверьте WKWebsiteDataStore каждой веб-страницы и при необходимости протестируйте реальные запросы.
Нужен выделенный физический Mac с удалённым доступом?
Сравните конфигурации M4 Start, M4 Work и M4 Pro, расположение узлов и периоды оплаты, чтобы выбрать подходящий вариант.