Шаг загрузки артефакта без if: always() пропускается когда тесты падают: именно в тот момент когда HTML-отчёт и скриншоты нужны больше всего. Отчётность в CI состоит из трёх независимых слоёв: хранение артефактов для инспекции после прогона, JUnit XML для парсинга дашбордами, и комментарии к PR где разработчики смотрят на результаты. Статья разбирает все три с примерами для GitHub Actions и GitLab, плюс уведомления в Slack и программный парсинг результатов.
Проблема с сырым выводом тестов
Вывод CI при падении Playwright по умолчанию выглядит так:
✘ tests/login.spec.ts > shows error for wrong password (15234ms)
Error: Timed out 5000ms waiting for expect(locator).toBeVisible()
Call log:
- expect.toBeVisible with timeout 5000ms
- waiting for locator('[data-testid="error-message"]')Чтобы найти все падения, нужно просматривать сотни строк. В сьюте из 200 тестов это занимает время.
Форматы отчётов
JUnit XML
Универсальный формат: каждая CI-система его понимает.
<?xml version="1.0" encoding="UTF-8"?>
<testsuites>
<testsuite name="Login Tests" tests="5" failures="1" time="12.5">
<testcase name="shows error for wrong password" time="15.2">
<failure message="Timed out waiting for error-message">
Error: Timed out 5000ms waiting for expect(locator).toBeVisible()
...
</failure>
</testcase>
<testcase name="successful login redirects to dashboard" time="3.2" />
</testsuite>
</testsuites>Генерация из Playwright
// playwright.config.ts
reporter: [
['junit', { outputFile: 'test-results/results.xml' }],
['list'], // Параллельный вывод в консоль
],HTML-отчёт
Встроенный HTML-отчёт Playwright: визуальный, со скриншотами и трейсами.
reporter: [
['html', { outputFolder: 'playwright-report', open: 'never' }],
],Сгенерировать и открыть локально:
npx playwright show-reportJSON
Машиночитаемый формат для кастомной обработки:
reporter: [
['json', { outputFile: 'test-results/results.json' }],
],Подходит для построения собственных дашбордов или программного парсинга результатов.
Несколько репортёров одновременно
Обычно нужно несколько форматов сразу:
reporter: process.env.CI
? [
['junit', { outputFile: 'test-results/results.xml' }],
['html', { outputFolder: 'playwright-report', open: 'never' }],
['list'], // Живой вывод в логах CI
]
: [
['html'], // Локально: только визуальный отчёт
['list'],
],Интеграция с GitHub Actions
Базовая настройка с загрузкой артефактов
name: Playwright Tests
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
cache: npm
- run: npm ci
- run: npx playwright install --with-deps chromium
- name: Run tests
run: npx playwright test
env:
BASE_URL: ${{ secrets.STAGING_URL }}
# Всегда загружаем отчёт, даже если тесты упали
- name: Upload HTML report
uses: actions/upload-artifact@v4
if: always()
with:
name: playwright-report
path: playwright-report/
retention-days: 30
- name: Upload JUnit results
uses: actions/upload-artifact@v4
if: always()
with:
name: test-results
path: test-results/results.xmlif: always() обязателен: без него шаг загрузки пропускается при падении тестов.
JUnit-аннотации в GitHub
GitHub Actions умеет парсить JUnit XML и аннотировать PR прямо в диффе:
- name: Report test results
uses: dorny/test-reporter@v1
if: always()
with:
name: Playwright Tests
path: test-results/results.xml
reporter: java-junit
fail-on-error: trueВ итоге в интерфейсе PR появляется сводка, а упавшие тесты аннотируются в коде.
Интеграция с GitLab CI
GitLab поддерживает JUnit нативно:
test:
image: mcr.microsoft.com/playwright:v1.44.0-jammy
script:
- npm ci
- npx playwright test
artifacts:
when: always
reports:
junit: test-results/results.xml # Нативная поддержка JUnit
paths:
- playwright-report/ # HTML-отчёт тоже загружаем
expire_in: 1 weekGitLab читает JUnit XML и показывает сводку по тестам прямо в UI пайплайна: никаких дополнительных инструментов не нужно.
Уведомления в Slack
Отправка сводки об ошибках в Slack когда CI падает:
- name: Notify Slack on failure
if: failure()
uses: slackapi/slack-github-action@v1
with:
payload: |
{
"text": "❌ Playwright tests failed on ${{ github.ref_name }}",
"attachments": [{
"color": "danger",
"fields": [
{
"title": "Branch",
"value": "${{ github.ref_name }}",
"short": true
},
{
"title": "Run",
"value": "${{ github.run_id }}",
"short": true
},
{
"title": "Report",
"value": "${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}"
}
]
}]
}
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK }}Программный парсинг результатов
Иногда нужна кастомная логика: например, уведомлять только о новых падениях, а не о повторных.
// scripts/parse-results.js
const fs = require('fs');
const xml2js = require('xml2js');
async function parseResults() {
const xml = fs.readFileSync('test-results/results.xml', 'utf8');
const result = await xml2js.parseStringPromise(xml);
const testsuites = result.testsuites.testsuite;
const failures = [];
testsuites.forEach(suite => {
(suite.testcase || []).forEach(testcase => {
if (testcase.failure) {
failures.push({
name: testcase.$.name,
suite: suite.$.name,
message: testcase.failure[0].$.message,
time: testcase.$.time,
});
}
});
});
console.log(`Total failures: ${failures.length}`);
failures.forEach(f => {
console.log(`- [${f.suite}] ${f.name}`);
console.log(` ${f.message}`);
});
process.exit(failures.length > 0 ? 1 : 0);
}
parseResults();Комментарий с результатами к PR
Автоматически добавлять сводку тестов комментарием к каждому PR:
- name: Test summary comment
uses: marocchino/sticky-pull-request-comment@v2
if: always()
with:
header: test-results
message: |
## Test Results
${{ steps.test-results.outputs.summary }}
[View full report](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }})Хранение и история результатов
Результаты полезны только если хранить их достаточно долго.
GitHub Actions
- uses: actions/upload-artifact@v4
with:
retention-days: 30 # Хранить 30 днейДля более долгой истории
Allure TestOps: платно, история без ограничений. Self-hosted S3/GCS bucket: хранить results.json и строить отчёты с трендами. Datadog/Grafana: отправлять метрики из прогонов для дашбордов.
Что должен содержать каждый CI-отчёт
Полезный отчёт отвечает на шесть вопросов:
1. Сколько тестов прошло, упало, пропущено? Сводные цифры
2. Какие тесты упали? Полный список с именами
3. Почему упали? Сообщения об ошибках и стек-трейсы
4. Как это выглядело? Скриншоты для UI-тестов
5. Сколько времени заняло? Время выполнения по каждому тесту
6. Это новое падение? Сравнение с предыдущим прогоном
HTML-отчёт Playwright закрывает пункты 1–5. Пункт 6 добавляют инструменты с трендами: Allure, TestOps.
Сводка по форматам
| Формат | Для чего |
|--------|---------|
| JUnit XML | Интеграция с CI, аннотации к PR |
| HTML | Чтение людьми, скриншоты, трейсы |
| JSON | Кастомная обработка, дашборды |
| List | Вывод в реальном времени в консоль |
Минимальная настройка для CI
Генерировать JUnit XML (CI умеет его парсить), загружать HTML-отчёт как артефакт, использовать if: always() на шагах загрузки.
Расширенная настройка
- Allure-отчёт с трендами
- Slack-уведомление при падении
- Комментарий к PR со сводкой
- Аннотации тестов прямо в диффе