Обновление Скриншотов прямо в CI
Обзор
Данная функция позволяет ревьюерам приниматьть новые эталонные скриншоты прямо из статического HTML-отчёта, опубликованного в CI (например, в S3 или на внутреннем веб-сервере). Интерфейс выглядит примерно так:

Данная функция очень похожа на GUI режим — вы можете принимать скриншоты и там, и там. Главное отличие заключается в том, что вы не можете запускать GUI в CI, но используя данную функцию это становится возможным. Благодаря этому можно экономить время, так как больше не нужно будет обновлять скриншоты, коммитить их вручную и выгружать в PR.
Типичный сценарий выглядит так:
- CI-прогон завершает тесты, генерирует сборку html-reporter и выкладывает статический отчёт в доступное ревьюерам место.
- Ревьюер открывает этот отчёт, помечает скриншоты, которые должны стать новыми эталонами, и нажимает Commit.
- html-reporter упаковывает выбранные скриншоты вместе с метаданными репозитория и отправляет их на принадлежащий вам сервис. Этот сервис работает постоянно (например, в вашем кластере или как облачная функция) и обновляет pull request новыми эталонами.
В режиме GUI эта функция выключена, потому что локальный интерфейс уже умеет принимать скриншоты напрямую. Чтобы элементы UI принятия скриншотов появились в статическом отчёте, необходимо задать URL репозитория, pull request'а и сервиса, как показано ниже.
Предварительная настройка
Добавьте блок staticImageAccepter в конфигурацию репортера, которая используется при сборке статического отчёта:
plugins: {
'html-reporter/hermione': {
enabled: true,
// ...другие настройки репортера
staticImageAccepter: {
enabled: true,
repositoryUrl: 'https://github.com/org/project',
pullRequestUrl: 'https://github.com/org/project/pull/42',
serviceUrl: 'https://accepter.example.com/static-accepter',
meta: {
ciRunId: process.env.GITHUB_RUN_ID,
// дополнительные данные, которые нужны вашему сервису
},
axiosRequestOptions: {
timeout: 120000
}
}
}
}
- Элементы для принятия скриншотов в статическом отчёте не появятся, если не задан
enabledили отчёт открыт не в статическом режиме.repositoryUrl,pullRequestUrlиserviceUrlобязательны; без них кнопка «Accept» недоступна. - Сохраняемые изображения всегда ссылаются на путь эталона. html-reporter выбросит ошибку, если инструмент не предоставляет
refImg.relativePath, потому что сервису нужен конечный относительный путь для каждого файла. - Параметр
axiosRequestOptions(необязательный) прокидывается в HTTP-клиент интерфейса отчёта — так можно настроить таймауты, заголовки или авторизацию, требуемые вашим сервисом.
Общая схема работы
- Ревьюер просматривает статический отчёт, отмечает скриншоты и открывает диалог Commit.
- html-reporter собирает выбранные элементы, загружает бинарные данные каждого «actual»-скриншота и формирует
multipart/form-data, содержащий метаданные репозитория, сообщение коммита и файлы изображений. - Этот payload отправляется POST-запросом на
serviceUrl. Прогресс загрузки отображается в интерфейсе. - Ваш сервис аутентифицирует запрос, проверяет его, сохраняет загруженные скриншоты и обновляет ветку PR (например, создаёт коммит или открывает follow-up PR). Он работает постоянно, поэтому его можно переиспользовать для разных отчётов и CI-запусков.
- Любой HTTP-статус в диапазоне
[200, 400)считается успехом: интерфейс помечает изобр ажения как принятые, запоминает их идентификаторы вlocalStorage, чтобы не отправлять повторно, и показывает уведомление. Ошибки приводят к отображению сообщения об ошибке.
Контракт HTTP API
html-reporter всегда отправляет один POST-запрос на staticImageAccepter.serviceUrl. Тело запроса имеет тип multipart/form-data.
Поля формы
| Имя | Тип | Описание |
|---|---|---|
repositoryUrl | текст | Полный URL Git-репозитория с эталонами (обычно целевой репозиторий PR). |
pullRequestUrl | текст | Полный URL pull request'а, который нужно обновить. Используйте его, чтобы определить ветку и владельца внутри сервиса. |
message | текст | Сообщение коммита, выбранное ревьюером. По умолчанию chore: update <tool> screenshot references. |
meta | текст (опционально) | Произвольная JSON-строка из staticImageAccepter.meta. Можно передавать контекст CI (ID прогона, автора, флаги). |
image | файл (повторяется) | Каждый выбранный скриншот передаётся бинарным файлом. Имя файла равно относительному пути в репозитории (например, test/screens/page/diff.png). |
Семантика запроса
- Файлы добавляются параллельно (до 256 одновременных загрузок), порядок не гарантируется — опирайтесь на
file.originalname(или аналог) вместо позиции. - Каждый файл заменяет соответствующий эталон (
ref) по указанному относительному пути. Именно сервис отвечает за запись контента в репозиторий или хранилище. - Клиент не отправляет отдельный JSON-список изображений — достаточно данных
multipart. - Механизмы аутентификации, авторизации и защиты от CSRF лежат на вашей стороне. Дополнительные заголовки можно задать через
axiosRequestOptions(например, bearer-токен).
Ожидаемый ответ
- Любой статус в диапазоне
[200, 400)считается успешным. Можно вернуть тело (например, JSON с ссылками на коммиты), но интерфейсу оно не требуется. - Остальные статусы или исключения приводят к ошибке в UI. Возвращайте развёрнутые сообщения, чтобы упрощать отладку.
Варианты реализации
Сервис принятия может обновлять PR либо от имени ревьюера, либо от имени бота. Ниже приведены два подхода — выберите тот, что лучше вписывается в ваши процессы, и адаптируйте примеры под свой стек, соблюдая HTTP-контракт.