Визуальное QA как этап конвейера CI
Месяц назад я смержил PR. Code review выглядел хорошо. Тесты прошли. Потом я открыл сайт на своём телефоне, и боковая панель была полностью сломана.
Исправление было тривиальным — отсутствующая media query. Баг был очевиден, как только ты действительно посмотрел на мобильный вид. Никто не посмотрел.
Поэтому я добавил этап конвейера, который смотрит.
Я открываю issue на GitHub, которое говорит:
Реализовать опцию Ручного Ввода для добавления клиентов. При клике открывается широкий выдвижной ящик с формой для создания нового клиента.
Один коммит спустя, PR прилетает с 30+ скриншотами, доказывающими, что каждое состояние работает на каждом viewport. Нулевое ручное тестирование. Единственное усилие — написать описание функции.
Концепция
При каждом push в PR, workflow GitHub Actions:
- Запускает Docker Compose стек (Django + Postgres) из ветки PR
- Выполняет миграции и создаёт тестового пользователя
- Передаёт локальный URL Claude Code с Playwright MCP (headless Chromium)
- Тестирует интерактивные элементы на основе PR diff
- Делает скриншоты каждого состояния на трёх viewport’ах
- Публикует результаты как комментарий к PR
Claude Code управляет браузером. Docker Compose предоставляет приложение. GitHub Actions связывает всё вместе. Ревьюеры видят доказательство функциональности без локального запуска чего-либо.
%%{init: {"flowchart": {"subGraphTitleMargin": {"top": 3, "bottom": 10}}} }%%
graph TD
A["Push в PR"] --> B
subgraph GHA["GitHub Actions Runner"]
B["Checkout + Docker Compose"] --> C["Миграции + Тестовые Данные"]
C --> Agent
subgraph Agent["Claude Code + Playwright"]
direction LR
D["Читать PR Diff"] --> E["Сопоставить Файлы → URLs"]
E --> F["Вход + Навигация"]
F --> G["Скриншот ×3 Viewport'а"]
end
end
Agent --> H["Комментарий к PR"]
style GHA fill:transparent,stroke:#888
style Agent fill:transparent,stroke:#888
Сам workflow прямолинеен:
name: UI Screenshots
on:
pull_request:
types: [opened, synchronize]
jobs:
screenshots:
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
steps:
- uses: actions/checkout@v4
# ... Настройка Docker Compose, миграции, создание тестового пользователя ...
- name: Start services
run: |
docker compose -f docker-compose.local.yml up -d --wait
- name: Install Playwright
run: npx playwright install --with-deps chromium
- name: Run Claude for Screenshots
uses: anthropics/claude-code-action@v1
with:
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
claude_args: >-
--allowed-tools
"mcp__playwright__*,
Bash(gh pr diff:*),
Bash(gh pr comment:*),
Read,Glob,Grep"
prompt: |
You are a visual QA agent. Your job is to visually
verify a UI deployment.
PR: ${{ github.event.pull_request.number }}
The Django app is running at http://localhost:8001
1. Run `gh pr diff` to find changed templates/views
2. Map changed files to URLs — only test pages
affected by this push
3. Login, then screenshot each page at three viewports:
1440x900, 1024x768, 640x1136
4. Save screenshots with descriptive filenames
5. Post a PR comment with:
- Each test performed with pass/fail
- Any visual issues found
- Total screenshots captured
Матрица Viewport’ов
| Устройство | Размер | Почему |
|---|---|---|
| Десктоп | 1440×900 | Стандартный ноутбук |
| Планшет | 1024×768 | iPad портретная ориентация |
| Мобильный | 640×1136 | iPhone SE |
Каждое интерактивное состояние захватывается на всех трёх. Компонент боковой панели генерирует 12+ скриншотов:
- Загрузка страницы (закрыто) — 3 viewport’а
- Клик по триггеру — 3 viewport’а
- Полностью открыто — 3 viewport’а
- Анимация закрытия — 3 viewport’а
Умножьте на светлую/тёмную темы, и вы смотрите на 24 скриншота для одного компонента. В этом суть. Исчерпывающее визуальное доказательство как артефакт CI.
Что Это Реально Выдаёт
Вот реальный вывод из PR, который добавил страницу ручного ввода. Агент публикует комментарий к PR со структурированным резюме, скриншотами на каждом viewport и чек-листом тестов:

Под этим резюме агент добавляет скриншоты каждого протестированного состояния. Вот десктопный вид — список клиентов с данными, плюс открытый ящик Добавить Клиента:

Агент протестировал:
- Состояние пустой формы
- Форму с ошибками валидации (отсутствует обязательное поле имени)
- Успешную отправку формы
- Рендеринг списка клиентов с данными
- Fallback’и аватаров при отсутствии изображения
- Переходы открытия/закрытия модального окна
- Все три viewport’а
Я пропустил индивидуальные состояния фокуса полей для этого PR — убывающая отдача на простой форме. Агент может это сделать, но 50 скриншотов текстовых полей, получающих фокус, не является полезным сигналом.
Комментарий к PR становится визуальным changelog’ом. Каждый запуск теста заканчивается чек-листом:

Через шесть месяцев я могу посмотреть на любой PR и увидеть точно, как выглядел UI при отправке.
Манифест Тестов
Промпт в workflow выше — общий, он работает для любого PR. Специфичность приходит из самого diff. Агент читает gh pr diff, выясняет, какие шаблоны и представления изменились, сопоставляет их с URL и решает, что тестировать.
Для компонента формы это означает, что он будет тестировать:
component: ClientForm
states:
- empty form
- filled form (valid data)
- validation error (submit without name)
- submitting (loading state)
- success (confirmation)
interactions:
- submit empty form (trigger validation)
- fill all fields, submit
- verify client appears in list after submit
Ручной план тестирования не нужен. Агент выводит тестовую поверхность из изменений кода.
Почему Скриншоты в Комментариях к PR
Встроенные скриншоты в комментарии к PR имеют несколько преимуществ:
- Видимые — ревьюеры видят их без клика на артефакты
- Контекстуальные — прямо рядом с diff, который они ревьюят
- Сравнимые — каждый push обновляет комментарий, так что вы видите до/после
- Проверяемые — история комментариев показывает, что было протестировано на каждом коммите
Что Это Ловит
Реальные баги, пойманные в первую неделю:
- Dropdown обрезан родителем с
overflow: hidden(видно только на планшете) - Текст кнопки невидим в тёмном режиме (неправильная CSS переменная)
- Метки формы смещены на iPhone (проблема с flexbox gap)
- Состояние hover застряло после touch (нужен был
@media (hover: hover)) - Задник модального окна не покрывает полный viewport на планшете в альбомной ориентации
Каждый из них прошёл code review. Каждый был очевиден на скриншотах.
Цена
Запуск Claude с Playwright MCP в CI занимает 2-4 минуты в зависимости от того, сколько состояний нужно протестировать. Для типичного PR, затрагивающего один компонент, это около 90 секунд.
Сравните с: развёртыванием в продакшн и узнаванием от пользователей, что мобильный layout сломан. Бесценно.
Сдвиг
Визуальное QA всегда было узким местом. Вы можете автоматизировать unit-тесты, интеграционные тесты, даже end-to-end потоки — но кто-то всё ещё должен посмотреть на UI. Это было правдой десятилетиями.
Это больше не правда. Агенты с браузерами не просто запускают скрипты. Они интерпретируют, навигируют, взаимодействуют и судят. Тестовая поверхность не захардкожена — она выводится из изменения. Каждый PR получает исчерпывающее визуальное покрытие, которое не может сравниться ни с каким ручным процессом.
Это меняет QE с ворот на гарантию. Не “кто-то это проверил”, а “конвейер это проверил, вот доказательство”. Каждый PR, каждый push, каждый viewport. Роль QE не исчезает. Она движется вверх по потоку. Вместо выполнения планов тестирования вы определяете, о чём должен заботиться агент. Вместо ловли багов вы проектируете систему, которая их ловит.