После слияния изменений, подготовленных удалённой командой, чаще всего упускают не путь к Chromium, а особенности Safari в работе с формами, фокусом и вёрсткой. Вместо того чтобы перед каждым выпуском открывать удалённый рабочий стол и вручную проверять страницы, лучше поддерживать на облачном Mac от GPUMini постоянный набор регрессионных тестов Safari WebDriver. Для каждого релиз-кандидата он будет выполнять одни и те же критические сценарии, а при сбое сохранять состояние страницы для последующего анализа.
Сначала задайте воспроизводимые границы выполнения
Автоматизация Safari зависит от графического сеанса macOS, поэтому запускать её по той же схеме, что и браузер без графического интерфейса, нельзя. Создайте отдельного тестового пользователя, оставляйте его графический сеанс активным и не допускайте одновременного выполнения тестов и ручной работы через удалённый доступ. В одном пользовательском профиле одновременно должен работать только один сеанс Safari, иначе история, загруженные файлы и состояние окон будут влиять на результаты других запусков.
При первоначальной подготовке узла включите драйвер и запустите диагностику из интерактивного терминала:
sudo safaridriver --enable
safaridriver --diagnose
mkdir -p "$HOME/browser-tests/artifacts"
Команда --enable относится к подготовке узла, поэтому её не следует включать в каждое автоматическое задание. При обычных запусках достаточно диагностики. Если она завершается с ошибкой, остановите тестирование, а не создавайте серию малоинформативных отчётов о тайм-аутах. Версии зависимостей среды Python также необходимо зафиксировать. Укажите конкретную версию selenium в файле зависимостей проекта вместо установки последней версии при каждом запуске.
Первая базовая характеристика браузерных регрессионных тестов — не просто возможность запустить браузер, а одинаковый результат для одного и того же коммита, набора тестовых данных и условий ожидания.
Исключите внешние колебания с помощью локальных фикстур
Сквозные сценарии входа, поиска и оформления заказа могут обращаться к тестовой среде, однако взаимодействие компонентов и совместимость с браузером по возможности следует проверять на локальных фикстурах. Поместите в репозиторий страницы с формами, диалоговыми окнами, загрузкой файлов и клавиатурной навигацией, а затем обслуживайте их через loopback-интерфейс. Это исключит влияние DNS, сертификатов и нестабильности внешних API.
set -euo pipefail
ROOT="$(cd "$(dirname "$0")" && pwd)"
ARTIFACTS="$ROOT/artifacts"
mkdir -p "$ARTIFACTS"
python3 -m http.server 8080 \
--bind 127.0.0.1 \
--directory "$ROOT/fixtures" \
>"$ARTIFACTS/http-server.log" 2>&1 &
SERVER_PID=$!
cleanup() {
kill "$SERVER_PID" 2>/dev/null || true
}
trap cleanup EXIT INT TERM
"$ROOT/.venv/bin/python" "$ROOT/tests/safari_smoke.py"
Перед назначением фиксированного порта проверьте его занятость командой lsof -nP -iTCP:8080 -sTCP:LISTEN. Если один узел используется несколькими заданиями, не завершайте процессы без разбора. Обеспечьте взаимное исключение на уровне планировщика либо назначьте каждому заданию отдельный порт и укажите его в конфигурации тестов.
Выражайте условия ожидания через состояние бизнес-сценария
Один из самых распространённых источников нестабильности — фиксированная пауза в несколько секунд после щелчка. Даже небольшое изменение нагрузки на машину приводит к ложным сбоям, если пауза слишком короткая, а чрезмерно долгая пауза замедляет весь набор тестов. Вместо этого ожидайте наблюдаемого состояния бизнес-сценария: например, доступности кнопки для нажатия, появления списка результатов или изменения текста статуса на ожидаемое значение.
from pathlib import Path
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
artifacts = Path("artifacts")
driver = webdriver.Safari()
driver.set_page_load_timeout(20)
try:
driver.get("http://127.0.0.1:8080/login.html")
wait = WebDriverWait(driver, 12)
wait.until(
EC.visibility_of_element_located((By.ID, "email"))
).send_keys("tester@local.invalid")
driver.find_element(By.ID, "continue").click()
status = wait.until(
EC.visibility_of_element_located((By.ID, "result"))
)
assert status.text == "Ready"
finally:
driver.quit()
Для локаторов используйте прежде всего стабильные значения id или специальные тестовые атрибуты. Не полагайтесь на иерархические селекторы, которые меняются при корректировке вёрстки. Проверки должны подтверждать результат, а не только наличие элемента. Наличие кнопки отправки не означает, что отправка прошла успешно, а наличие контейнера списка — что данные уже отрисованы.
Сохраняйте достаточно данных для восстановления каждого сбоя
Самый дорогостоящий результат браузерного теста — сообщение «CI завершился по тайм-ауту, но контекст не сохранился». При сбое каждого тестового сценария сразу сохраняйте снимок экрана, исходный код страницы, текущий URL, имя сценария и временные данные. Если делать снимок только после завершения всего набора, на нём часто окажется страница, которая уже была закрыта или перенаправлена по другому адресу.
| Проявление сбоя | Что сохранить в первую очередь | Что проверить в первую очередь |
|---|---|---|
| Элемент так и не стал видимым | Снимок экрана, исходный код страницы | Перекрывающий слой, адаптивную вёрстку, локатор |
| Щелчок не дал результата | Текущий URL, состояние кнопки | Отключённое состояние, фокус, привязку событий |
| Истёк тайм-аут загрузки страницы | URL, журналы сервиса | Локальный сервис, перенаправления, блокировку ресурсов |
| Драйвер не может создать сеанс | Диагностику драйвера | Графический сеанс, оставшиеся процессы, разрешения |
Имя файла снимка экрана должно содержать идентификатор тестового сценария и уникальный номер запуска, но не токены, адреса электронной почты или секреты проекта. Исходный код страницы также может содержать тестовые данные, поэтому перед архивацией его необходимо обезличить и задать чёткий срок хранения.
Немедленно записывайте данные в момент исключения
Обработчик сбоя в тестовом фреймворке должен вызывать единую функцию сбора свидетельств. Само сохранение также может завершиться ошибкой, поэтому ошибки записи снимка экрана и исходного кода страницы нужно обрабатывать отдельно. Ошибка архивации не должна скрывать исходную ошибку проверки.
Восстанавливайте оставшиеся сеансы вместо слепых повторов
После прерывания задания Safari или процесс драйвера могут продолжить удерживать сеанс. Если при следующем запуске сразу повторить попытку, часто возникает серия ошибок создания сеанса. Сначала проверьте, не осталось ли процессов, запущенных тестовым пользователем, и убедитесь, что другие штатные тесты не выполняются. Только после этого завершайте оставшийся сеанс. На общем узле нельзя массово останавливать процессы по слишком общему имени.
Повторная попытка допустима только при известной временной ошибке создания сеанса и не более одного раза. Ошибки проверок, изменения структуры страницы и несоответствие состояния бизнес-сценария нельзя автоматически обходить повторными запусками: так реальная регрессия может быть замаскирована под случайный сбой. Если единственная повторная попытка оказалась успешной, зарегистрируйте её отдельно как событие нестабильности, а не отмечайте всё задание как полностью исправное.
Наконец, разделите регрессионный барьер на два уровня: при каждом коммите запускайте дымовые тесты на локальных фикстурах, а для релиз-кандидата — небольшой набор критических сквозных сценариев. Перед выпуском убедитесь, что диагностика драйвера проходит успешно, тестовый пользователь работает монопольно, сервис фикстур доступен, явные ожидания не содержат фиксированных пауз, данные о сбоях можно прочитать, а функция очистки обязательно выполняется. В результате вы получите не набор браузерных сценариев, которые «иногда проходят», а устойчивую базовую линию регрессионного тестирования Safari, позволяющую отслеживать изменения в долгосрочной перспективе.
Часто задаваемые вопросы
Можно ли запускать Safari WebDriver без активного графического сеанса?
Такой режим не следует считать надёжным. Выделите отдельного тестового пользователя, оставьте графический сеанс macOS активным и проверяйте драйвер перед заданием.
Стоит ли параллельно запускать несколько сеансов Safari на одном Mac?
Базовую схему лучше строить с одним последовательным сеансом. Для ускорения распределяйте группы тестов по независимым узлам, не разделяя один профиль браузера.
Что сохранять при падении браузерного теста?
Сохраните снимок экрана, исходный код страницы, текущий URL, имя теста и результат диагностики драйвера. Журналы приложения прикладывайте только после удаления секретов.
Как выбрать облачный Mac для разработки и сборки
Сравните две конфигурации M4, четыре срока аренды и четыре доступных узла, а затем выполните развертывание под задачи проекта.