Playwright для Directum RX, часть 3: каждое утро по расписанию, уведомление при падении и время шагов в Prometheus
Третья часть серии об автотестах веб-клиента Directum RX на Playwright. Запуск по расписанию в CI из готового образа, уведомление при падении, свой репортёр, который пишет время шагов в метрики Prometheus через node_exporter, и правила тревоги.
Коротко. Готовый образ Playwright, расписание в CI до начала рабочего дня, уведомление при падении туда, куда приходят тревоги. Свой репортёр пишет успех и время шагов в textfile-коллектор node_exporter, а три правила ловят падение, замедление вдвое против недели и отсутствие запусков.
Тесты из первой и второй частей полезны, только если запускаются сами и о падении кто-то узнаёт. Здесь доводим их до работы без человека: запуск каждое утро до начала рабочего дня, сообщение при падении и время каждого шага в мониторинге рядом с метриками серверов.
Симптом
- Автотесты есть, но их запускают руками перед релизом, а в остальное время они молчат.
- Прогон по расписанию падает неделю подряд, отчёт лежит в CI, и его никто не открывает.
- Пользователи жалуются, что вход стал дольше, а когда это началось, сказать нечем.
Почему так
Автотест, который запускается по расписанию, по сути становится проверкой из мониторинга: он ходит в систему как пользователь и проверяет, что работа делается. Значит, и обращаться с ним нужно как с мониторингом. Результат должен приходить туда же, куда приходят тревоги, а не лежать в CI. Время шагов нужно хранить как ряд, чтобы видеть тренд. И отдельно нужна тревога на случай, когда тест вообще перестал запускаться.
Запуск в CI
У Playwright есть готовый образ со всеми браузерами и зависимостями, ставить ничего не нужно. Версия образа должна
совпадать с версией @playwright/test в проекте. Пример для GitLab CI, расписание задаётся в настройках проекта
(CI/CD → Schedules), например в 07:00 по будням:
smoke:
image: mcr.microsoft.com/playwright:v1.55.0-noble
rules:
- if: $CI_PIPELINE_SOURCE == "schedule"
script:
- npm ci
- npx playwright test
after_script:
- |
if [ "$CI_JOB_STATUS" != "success" ]; then
curl -s -X POST "https://api.telegram.org/bot${TG_TOKEN}/sendMessage" \
--data-urlencode chat_id="${TG_CHAT}" \
--data-urlencode text="Утренняя проверка RX упала: ${CI_JOB_URL}"
fi
artifacts:
when: always
paths: [playwright-report/, test-results/]
expire_in: 14 days
В Jenkins то же самое делается триггером cron('H 7 * * 1-5') и шагом post { failure { … } }. В закрытом контуре
образ кладут в свой реестр, а уведомление шлют туда, куда у вас приходят тревоги: в почту, в мессенджер
или через Alertmanager.
Пароли тестовых учёток задаются секретными переменными CI, в репозитории их нет. Отчёт и трассы сохраняются как артефакты при любом исходе: по трассе упавшего теста причину обычно видно без повторного запуска.
Время шагов в Prometheus
Уведомление скажет, что упало. Чтобы видеть, что медленно становится медленнее, время каждого шага пишется в метрики. Проще всего через textfile-коллектор node_exporter: тест пишет файл в формате Prometheus в каталог коллектора, node_exporter отдаёт его вместе с метриками сервера. Отдельный сервис не нужен.
Свой репортёр Playwright, файл reporters/prom.ts:
import type { Reporter, TestCase, TestResult, TestStep } from '@playwright/test/reporter';
import { writeFileSync, renameSync } from 'node:fs';
const q = (s: string) => s.replace(/\\/g, '\\\\').replace(/"/g, '\\"').replace(/\n/g, ' ');
export default class PromReporter implements Reporter {
private ok: string[] = [];
private dur: string[] = [];
private steps: string[] = [];
onTestEnd(test: TestCase, result: TestResult) {
if (test.parent.project()?.name === 'setup') return;
const t = q(test.title);
this.ok.push(`rx_ui_test_success{test="${t}"} ${result.status === 'passed' ? 1 : 0}`);
this.dur.push(`rx_ui_test_duration_seconds{test="${t}"} ${(result.duration / 1000).toFixed(3)}`);
// только шаги верхнего уровня: вложенные служебные (например, повторы toPass) Playwright тоже называет test.step
result.steps.filter((s: TestStep) => s.category === 'test.step').forEach((s) => {
this.steps.push(`rx_ui_step_duration_seconds{test="${t}",step="${q(s.title)}"} ${(s.duration / 1000).toFixed(3)}`);
});
}
onEnd() {
const out = process.env.PROM_TEXTFILE ?? 'rx_ui.prom';
const body = [
'# HELP rx_ui_test_success 1 если проверка прошла',
'# TYPE rx_ui_test_success gauge', ...this.ok,
'# HELP rx_ui_test_duration_seconds Длительность проверки',
'# TYPE rx_ui_test_duration_seconds gauge', ...this.dur,
'# HELP rx_ui_step_duration_seconds Длительность шага проверки',
'# TYPE rx_ui_step_duration_seconds gauge', ...this.steps,
'# HELP rx_ui_last_run_timestamp_seconds Время последнего прогона',
'# TYPE rx_ui_last_run_timestamp_seconds gauge',
`rx_ui_last_run_timestamp_seconds ${Math.floor(Date.now() / 1000)}`, '',
].join('\n');
writeFileSync(out + '.tmp', body);
renameSync(out + '.tmp', out); // node_exporter не прочитает файл наполовину записанным
}
}
Подключается в playwright.config.ts рядом со стандартным отчётом:
reporter: [['html', { open: 'never' }], ['./reporters/prom.ts']],
Если тесты запускаются на сервере, где стоит node_exporter с ключом --collector.textfile.directory=/var/lib/node_exporter/textfile,
запуск выглядит так: PROM_TEXTFILE=/var/lib/node_exporter/textfile/rx_ui.prom npx playwright test. Если в CI,
файл после прогона копируют на такой сервер или отправляют в Pushgateway.
Каждая метрика описана заголовками HELP и TYPE, строки одной метрики идут подряд. Формат мы проверили
promtool check metrics на файле, который репортёр записал после прогона тестов из второй части.
Тревоги
Три правила закрывают основное:
groups:
- name: rx-ui
rules:
- alert: RxUiCheckFailed
expr: rx_ui_test_success == 0
labels: { severity: critical }
annotations:
summary: "Проверка «{{ $labels.test }}» не прошла"
description: "Пользователи, скорее всего, не могут выполнить это действие. Отчёт и трасса в последнем прогоне CI."
- alert: RxUiCheckSlow
expr: rx_ui_step_duration_seconds > 2 * avg_over_time(rx_ui_step_duration_seconds[7d])
labels: { severity: warning }
annotations:
summary: "Шаг «{{ $labels.step }}» вдвое медленнее, чем за неделю"
description: "Сейчас {{ $value | humanizeDuration }}. Посмотрите время ответа и очереди за то же время."
- alert: RxUiCheckStale
expr: time() - rx_ui_last_run_timestamp_seconds > 26 * 3600
labels: { severity: warning }
annotations:
summary: "Утренняя проверка не запускалась больше суток"
description: "Расписание в CI выключено, раннер недоступен или тесты падают до записи метрик."
Последнее правило важнее, чем кажется. Без него сломанное расписание выглядит как отсутствие проблем: тревог нет, потому что нет и проверок. Порог 26 часов выбран под ежедневный запуск; если запуск только по будням, его поднимают до 74 часов, иначе тревога будет каждое воскресенье.
Время шагов удобно вывести на панель рядом с временем ответа веб-клиента и очередями: если вход стал дольше одновременно с ростом очереди, причина видна сразу. Почему сравнивать стоит по перцентилям, а не среднему, разобрано в статье про перцентили.
Чего не делать
- Не оставлять результат только в CI. Падение должно приходить туда же, куда приходят тревоги мониторинга.
- Не обходиться без тревоги на устаревание. Тест, который перестал запускаться, хуже, чем упавший.
- Не запускать каждые пять минут. Сценарии с данными создают задачи. Для частых проверок оставьте только вход и загрузку веб-клиента, а сценарии гоняйте раз в день.
- Не хранить пароли тестовых учёток в репозитории. Только секретные переменные CI.
Похожая картина у вас?
Разберём ваш контур за 30 минут бесплатного созвона: версия RX, состав сервисов, что болит сильнее всего.
Написать