2026: фронтенд на удалённом Mac —
агрегация JUnit XML, индекс вложений trace и stderr в одну сводку ворот PR через OpenClaw
Задача: после прогона Playwright на удалённом Mac ревьюеру нужна одна проверяемая сводка ворот PR: объединённый JUnit XML, индекс trace (путь и размер, без заливки целого архива в текст) и фрагменты stderr, привязанные к упавшим кейсам. Ниже — HowTo с четырьмя блоками H2: соглашение о путях, пороги, шаблон на стороне шлюза и повтор при сбоях; плюс идея исполняемых скриптов. Оркестрацию шардов и merge-reports разбираем в статье про шарды; минимальные trace и HAR — в playbook по trace; токены смоука — в E2E и токены. Открытый блог, главная и страница покупки доступны без входа.
Три типичных боли
- Шум от нескольких XML. По файлу на шард — в PR не видно целостной картины passed/failed/skipped.
- Раздувание из trace. Вставка zip или base64 в вебхук ломает лимиты и раскрывает привычки путей.
- Потеря сигнала в stderr. Полный лог нечитабелен; нужны короткие куски рядом с именем теста из JUnit.
01 Соглашение о путях отчётов
Зафиксируйте одинаковые относительные пути в репозитории для CI и для удалённого Mac: например artifacts/e2e/junit/shard-{n}/results.xml, каталог artifacts/e2e/traces/ и агрегат artifacts/e2e/merged/summary.json рядом с merged/results.xml. Рядом положите run_meta.json с версией Playwright, коротким SHA и именем проекта — без секретов и токенов.
Если на арендованном томе работают несколько веток, добавьте к корню префикс с OPENCLAW_RUN_ID или CI_PIPELINE_ID, чтобы параллельные прогоны не перезаписали чужие results.xml. Тот же префикс прокиньте в заголовки вебхука — так проще отладить «пустой merge» на стороне шлюза.
HowTo: включите junit reporter в конфиге или CLI; каждый job/shard пишет в свой подкаталог; финальный шаг на агрегаторе читает всё дерево artifacts/e2e/junit/, мерджит XML, затем сканирует trace и пишет trace_index.json только с метаданными. Лог раннера сохраняйте в файл, чтобы вторым проходом вырезать stderr по таймстемпам или маркерам кейсов.
Имя npm-скрипта или цели Makefile для merge должно быть одним и тем же в README и runbook — иначе локальный запуск и раннер на Mac разъедутся уже на первом шаге.
02 Пороговая стратегия
В summary.json закрепите поля, которые читает ворота: failed (любое ненулевое — красный merge), доля skipped, бюджет flake относительно скользящей медианы, и опционально slow_top_n для жёлтых предупреждений без блокировки. Коэффициенты версионируйте в JSON — не оставляйте «договорённости в чате».
| Поле | Ориентир | Действие ворот |
|---|---|---|
failed |
строго 0 для зелёного | таблица упавших тестов в PR |
skipped |
< 2% от total | жёлтый блок с объяснением |
| stderr на кейс | до ~80 строк или 2–4 КБ UTF-8 | обрезка с маркером «…» |
Ретраи самих тестов Playwright остаются в конфиге фреймворка; отдельно считайте ретраи публикации сводки в API платформы (см. следующий раздел), чтобы не смешивать метрики «флейк продукта» и «флейк доставки».
Для команд с ночными прогонами полезно хранить в том же JSON ссылку на baseline ветки main: тогда ворота PR сравнивают не только абсолютные числа, но и дельту к последнему зелёному коммиту — меньше ложных «жёлтых» из-за шума инфраструктуры на общем Mac.
03 Шаблон на стороне шлюза
OpenClaw должен получать компактный Markdown: первая строка — статус и тройка счётчиков; далее сворачиваемый блок с таблицей падений, первыми тремя строками индекса trace и картой «тест → stderr». Имена плейсхолдеров в шаблоне совпадают с ключами summary.json, чтобы шлюз не ветвился по типам отчётов.
Храните шаблон в репозитории, например .openclaw/templates/pr_gate_e2e.md, и прогоняйте его тем же ревью, что и код ворот. В теле комментария добавьте HTML-комментарий-якорь вида <!-- openclaw-e2e:${GIT_SHA} --> для последующих upsert.
Если шлюз допускает лёгкую генерацию текста моделью, передавайте ей уже нормализованный JSON (счётчики, топ-N имён тестов, три строки индекса trace) — сырой JUnit и мегабайты логов оставьте вне контекста. Так вы держите стоимость и задержку предсказуемыми при каждом push.
# Идея исполняемых шагов (Node/Bash) на агрегаторе после шардов:
node scripts/merge-junit-xml.mjs \
--inputs "artifacts/e2e/junit/shard-*/results.xml" \
--out-xml artifacts/e2e/merged/results.xml \
--out-json artifacts/e2e/merged/summary.json
node scripts/build-trace-index.mjs --traces artifacts/e2e/traces --out artifacts/e2e/merged/trace_index.json
node scripts/extract-stderr-snippets.mjs --log run.log --junit artifacts/e2e/merged/results.xml \
--out artifacts/e2e/merged/stderr_snippets.json
node scripts/openclaw-post-pr-summary.mjs \
--bundle artifacts/e2e/merged \
--template .openclaw/templates/pr_gate_e2e.md \
--idempotency "${GIT_SHA}:e2e-junit"
Раннер на Mac считает и режет данные; шлюз валидирует схему, режет полезную нагрузку и рендерит Markdown. Сырые архивы остаются в артефактах CI с подписанными URL, а не в теле запроса к модели или к API комментариев.
04 Повтор при ошибках и идемпотентность
При 429 или 5xx от API ограничьте серию, например, пятью попытками: базовая задержка 200 мс, удвоение до потолка 30 с и джиттер до 10 с. Логируйте каждую попытку одной структурированной строкой (attempt, HTTP-код, задержка). Если два раза подряд не удалось распарсить ответ шлюза, сохраните только локальные JSON и не спамьте PR.
- Заголовок
Idempotency-Key: параGIT_SHA+ идентификатор ворот (напримерe2e-junit). - Счётчик ретраев публикации добавьте в
summary.jsonдля недельных отчётов SRE. - Не смешивайте обновление комментария с повторным полным прогоном suite без необходимости — дорого на Apple Silicon под нагрузкой.
Когда слияние JUnit, индекс trace и усечённый stderr оформлены как скрипты и стабильные пути, одна сводка PR становится повторяемой и на CI, и на удалённом Mac. Нужен постоянный узел под WebKit и тяжёлые ворота — откройте тарифы, оформите аренду на странице покупки, затем вернитесь к списку блога и связанным материалам по шардам и trace.
Ещё материалы: каталог блога.