Шаг загрузки артефакта без 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-report

JSON

Машиночитаемый формат для кастомной обработки:

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.xml

if: 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 week

GitLab читает 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 со сводкой
  • Аннотации тестов прямо в диффе
→ See also: Allure отчёты для Playwright: настройка расширенной отчётности | Отчёты тестов Playwright: встроенный HTML против Allure | CI/CD для QA: сравнение GitHub Actions, Jenkins и GitLab