Запись видео при сбоях iOS UI-тестов на облачном Mac

DevOps и CI/CD ·~5 мин чтения

Запись видео при сбоях iOS UI-тестов на облачном Mac

Запись видео при сбоях iOS UI-тестов на облачном Mac

Когда UI-тест падает на облачном Mac, одних проверок и снимков экрана часто недостаточно. Всплывающее окно может появиться и сразу исчезнуть, переход между экранами — остановиться на середине, а клавиатура — перекрыть кнопку. Понять последовательность таких событий можно только по непрерывной записи. Надёжный подход заключается не в постоянном хранении всех видео, а в запуске записи экрана нужного симулятора до начала теста: при успехе видео удаляется, а при сбое архивируется вместе с журналами и пакетом результатов.

Зафиксируйте тестовое устройство и границы сбора данных

Не передавайте booted напрямую команде записи. Из-за параллельных заданий, устройства, оставшегося запущенным после аварийного завершения предыдущего прогона, или ручной отладки одновременно могут работать несколько симуляторов. В результате видео будет записано не с того устройства. Сначала создайте или найдите устройство, выделенное для текущего задания, получите его UDID, а затем передайте этот идентификатор на этапы запуска, тестирования, записи и очистки.

DEVICE_NAME="CI-iPhone"
RUNTIME="com.apple.CoreSimulator.SimRuntime.iOS-18-0"
DEVICE_TYPE="com.apple.CoreSimulator.SimDeviceType.iPhone-16"

UDID="$(xcrun simctl list devices available -j |
  plutil -extract devices json -o - - |
  jq -r --arg name "$DEVICE_NAME" '
    .. | objects | select(.name? == $name) | .udid
  ' | head -n 1)"

if [ -z "$UDID" ]; then
  UDID="$(xcrun simctl create "$DEVICE_NAME" "$DEVICE_TYPE" "$RUNTIME")"
fi

xcrun simctl boot "$UDID" 2>/dev/null || true
xcrun simctl bootstatus "$UDID" -b

Идентификатор среды выполнения зависит от установленных версий Xcode. Поэтому перед запуском конвейер должен проверять доступные среды командой xcrun simctl list runtimes, а не предполагать, что на всех машинах установлена одна и та же версия. Если несколько заданий выполняются параллельно, в имя устройства также следует включать номер задания, чтобы они не использовали общее состояние.

Запись нужна для восстановления хронологии сбоя, а не для замены журналов. Видео отвечает на вопрос «что сначала произошло в интерфейсе», а журналы и xcresult — на вопрос «что зафиксировал тестовый фреймворк».

Корректно завершайте процесс записи

simctl io recordVideo работает непрерывно, поэтому скрипт должен сохранить PID этого процесса. После завершения теста не отправляйте сразу сигнал принудительной остановки. Сначала отправьте INT, чтобы инструмент успел записать индекс видеоконтейнера, а затем дождитесь завершения процесса.

ARTIFACTS="${PWD}/artifacts"
VIDEO="${ARTIFACTS}/ui-test.mp4"
RESULT="${ARTIFACTS}/UITests.xcresult"
LOG="${ARTIFACTS}/xcodebuild.log"

mkdir -p "$ARTIFACTS"
rm -rf "$RESULT"
rm -f "$VIDEO" "$LOG"

xcrun simctl io "$UDID" recordVideo \
  --codec=h264 \
  --force "$VIDEO" &
RECORDER_PID=$!

finish_recording() {
  if kill -0 "$RECORDER_PID" 2>/dev/null; then
    kill -INT "$RECORDER_PID"
    wait "$RECORDER_PID" || true
  fi
}

trap finish_recording EXIT INT TERM

H.264 обычно удобно просматривать в браузере и прикладывать к заявкам. Если команде впоследствии потребуется покадровый анализ, можно выбрать другой кодек, предварительно убедившись в его совместимости с системой хранения артефактов. Однако не следует смешивать форматы в одном конвейере, иначе поведение инструментов просмотра и правил хранения станет непредсказуемым.

Сохраняйте настоящий код завершения теста

Запись экрана — это вспомогательный процесс, который не должен скрывать результат xcodebuild. Самый надёжный способ — временно отключить завершение скрипта при ошибке, отдельно сохранить код возврата теста, а затем штатно остановить запись. Если для вывода журнала используется tee, необходимо получить статус завершения xcodebuild внутри конвейера, а не ошибочно принять успешное завершение tee за успех теста.

set +e
xcodebuild test \
  -workspace App.xcworkspace \
  -scheme AppUITests \
  -destination "platform=iOS Simulator,id=${UDID}" \
  -resultBundlePath "$RESULT" \
  >"$LOG" 2>&1
TEST_STATUS=$?
set -e

finish_recording
trap - EXIT INT TERM

if [ "$TEST_STATUS" -eq 0 ]; then
  rm -f "$VIDEO"
  exit 0
fi

exit "$TEST_STATUS"

Скрипт намеренно не завершается сразу после падения теста, поскольку запись ещё нужно корректно закрыть. Для resultBundlePath следует использовать отдельный путь для каждого задания, удаляя старый каталог перед запуском. Иначе результаты предыдущего прогона могут смешаться с данными текущего сбоя.

Проверяйте видео, а не только наличие файла

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

Проверка Рекомендуемое действие Действие при ошибке
Файл существует test -f Сохранить журнал и отметить отсутствие записи
Файл не пуст test -s Проверить PID процесса записи и свободное место на диске
Длительность читается Запросить метаданные медиафайла Считать файл повреждённым и не загружать пустой контейнер
Устройство совпадает Сверить записанный UDID Остановить задание и удалить ошибочно использованное устройство
Пакет результатов теста Проверить каталог xcresult Сохранить исходный журнал в качестве резервного источника данных

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

Учитывайте параллельный запуск, тайм-ауты и оставшиеся процессы

У каждого задания должны быть собственные симулятор, каталог результатов и имя видеофайла. Общий booted, фиксированный путь artifacts/ui-test.mp4 или общий каталог DerivedData приведут к тому, что параллельные задания начнут перезаписывать файлы друг друга. Каталог задания можно сформировать на основе уникального номера, предоставленного конвейером, а при выходе удалить временные файлы успешно завершившихся заданий.

Если внешний механизм тайм-аута останавливает тест, функция очистки всё равно должна получить возможность выполниться. Сначала отправьте тестовому процессу сигнал завершения, который он способен обработать, оставьте несколько секунд на выполнение trap и корректное закрытие видео и только после этого запускайте принудительную очистку. Перед следующим заданием можно проверить наличие процессов записи simctl io, родительские процессы которых уже завершились. Однако не используйте общий killall, иначе можно случайно остановить другие задания на той же машине.

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

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

Почему не стоит использовать booted вместо UDID?

При параллельном запуске может работать несколько симуляторов. Явный UDID связывает запуск, тест, видеозапись и очистку с одним устройством.

Почему готовое видео иногда не открывается?

Принудительное завершение может оборвать запись индекса контейнера. Отправьте процессу сигнал INT и дождитесь его завершения перед проверкой файла.

Нужно ли хранить видео успешных тестов?

Обычно нет. После успешного запуска видео удаляют, а при сбое архивируют его вместе с журналом и пакетом xcresult.

Debug M4

Запустите следующую сборку на выделенном физическом узле Apple Silicon

Выберите срок аренды и регион узла, проверьте Mac Mini M4, 16 ГБ RAM, SSD 256 ГБ и сумму к оплате в USD, затем оформите заказ.

Арендовать облачный Mac