Python: Selenium

Теория: Отчёты и CI

Allure делает HTML-отчёты по тестам: шаги, статусы, вложения. Плагин allure-pytest позволяет Pytest записывать результаты в формат Allure. Утилита allure превращает результаты в HTML.

Настройка делится на два этапа:

  1. в проекте — установка allure-pytest и добавление вложений;
  2. в CI — запуск тестов, сбор результатов и публикация HTML-отчёта.

Установка зависимостей

Сначала устанавливается плагин для Pytest и утилита для сборки HTML-отчёта. Плагин allure-pytest добавляется через uv add — так зависимость попадает в pyproject.toml. Утилита allure — это отдельный бинарный инструмент, который устанавливается системно: через пакетный менеджер ОС, дистрибутив или контейнер.

Пример установки плагина:

## внутри проекта
uv add allure-pytest

Вариант через pipx:

pipx install allure-pytest
## если команда allure недоступна, отдельно устанавливается CLI

Проверка установки CLI:

allure --version

Если команда отсутствует, CLI ставится штатным способом для ОС или используется контейнер allure-commandline. Установка выполняется один раз.

Базовый запуск и каталог результатов

Pytest сохраняет сырые результаты в выбранный каталог, а утилита allure строит из них HTML-отчёт. Каталог с результатами и каталог с HTML-разметкой разделяются: по умолчанию используют allure-results и allure-report.

Пример запуска:

## запись результатов
uv run pytest -vv --alluredir=allure-results

## генерация HTML
allure generate allure-results -o allure-report --clean

## локальный просмотр
allure serve allure-results

Флаг --clean удаляет старое содержимое папки allure-report перед сборкой. Команда serve собирает отчёт и запускает временный веб-сервер для локального просмотра.

Метаданные тестов: фичи, истории, severity

Allure позволяет подписывать тесты понятными метками: к чему относится тест, что он проверяет, насколько он важен. Эти подписи ставятся прямо над тестом через декораторы. В отчёте они видны как заголовки и помогают быстро ориентироваться.

Пример:

@allure.feature("Авторизация")
@allure.story("Успешный вход по валидным данным")
@allure.severity(allure.severity_level.CRITICAL)
  • feature — к какой части приложения относится тест (здесь: авторизация);
  • story — что именно он проверяет (успешный вход);
  • severity — насколько тест важный.

Шаги внутри теста (with allure.step(...)) разбивают выполнение на понятные действия. В отчёте это становится списком: «Открыть страницу», «Ввести логин и пароль», «Проверить результат». Если тест падает, видно на каком шаге остановилось.

То есть Allure не просто показывает «тест упал», а показывает, что делал тест и на каком действии ошибка.

Вложения: скриншоты, DOM, логи браузера

При падении теста Allure может прикреплять данные из браузера. Это выполняется один раз в conftest.py. Хук проверяет статус выполнения и при ошибке получает driver. В отчёт добавляется скриншот, HTML текущей страницы и по возможности логи консоли браузера.

## tests/conftest.py
import pytest
import allure

@pytest.hookimpl(hookwrapper=True)
def pytest_runtest_makereport(item, call):
    outcome = yield
    rep = outcome.get_result()

    if rep.when == "call" and rep.failed:
        driver = item.funcargs.get("driver")
        if not driver:
            return
        allure.attach(driver.get_screenshot_as_png(), name="screen", attachment_type=allure.attachment_type.PNG)
        allure.attach(driver.page_source, name="dom", attachment_type=allure.attachment_type.HTML)
        try:
            entries = driver.get_log("browser")
            if entries:
                logs = "\n".join(f"{e.get('level')}: {e.get('message')}" for e in entries)
                allure.attach(logs, name="browser.log", attachment_type=allure.attachment_type.TEXT)
        except Exception:
            pass

Успешные тесты не затрагиваются. В отчёт попадает только информация с неуспешных запусков.

Конфигурация Pytest для единых правил

Общие параметры Pytest выносят в pytest.ini. Так сокращается команда запуска и одинаковый вывод гарантируется во всех средах.

## pytest.ini
[pytest]
addopts = -vv --tb=auto
testpaths = tests

Если нужно, чтобы результаты Allure писались всегда, в addopts добавляют --alluredir=allure-results. В CI этот параметр обычно передаётся прямо в шаге запуска.

Интеграция в GitHub Actions: артефакт и публикация

В CI это делается в два шага.

Сначала запускаются тесты и сохраняются результаты Allure в папку allure-results. Эти файлы загружаются как артефакт — чтобы второй шаг мог их забрать.

Потом создаётся отдельный шаг, который скачивает эти результаты и собирает из них HTML-отчёт. Для сборки используется готовый контейнер, внутри которого есть команда allure. После сборки папка allure-report загружается как артефакт. Её можно скачать прямо из интерфейса GitHub Actions. При желании можно сразу выкладывать отчёт на GitHub Pages.

Пример простого варианта в GitHub Actions:

## .github/workflows/tests.yml
name: tests
on: [push, pull_request]

jobs:
  run-tests:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v5
      - uses: actions/setup-python@v5
        with:
          python-version: '3.14'
      - run: pip install uv
      - run: uv sync --frozen
      - run: uv run pytest -vv --alluredir=allure-results
      - uses: actions/upload-artifact@v4
        with:
          name: allure-results
          path: allure-results

  build-report:
    needs: run-tests
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v5
      - uses: actions/download-artifact@v4
        with:
          name: allure-results
          path: allure-results
      - uses: docker://frankescobar/allure-docker-service:latest
        with:
          args: bash -lc "allure generate allure-results -o allure-report --clean"
      - uses: actions/upload-artifact@v4
        with:
          name: allure-report
          path: allure-report

То есть логика такая: тесты → результат сохранился → другой шаг собрал отчёт → отчёт можно скачать.

Интеграция в GitLab CI: кэши и артефакты

Логика такая же, как в GitHub Actions: тесты пишут результаты, отдельный шаг собирает HTML и прикрепляет его к пайплайну.

## .gitlab-ci.yml
stages: [test, report]

pytest:
  stage: test
  image: python:3.14
  script:
    - pip install uv
    - uv sync --frozen
    - uv run pytest -vv --alluredir=allure-results
  artifacts:
    when: always
    paths:
      - allure-results
    expire_in: 7 days

allure:
  stage: report
  image: frankescobar/allure-docker-service
  script:
    - allure generate allure-results -o allure-report --clean
  artifacts:
    when: always
    paths:
      - allure-report
    expire_in: 7 days
  needs: ["pytest"]

В результате отчет лежит как артефакт, его можно открыть прямо в GitLab.

Allure умеет показывать информацию об окружении. Если в allure-results есть файл environment.properties, отчет отображает значения: на каком стенде шли тесты, какой браузер, была ли голова, какая версия сборки.

Простейший файл:

env=stage
browser=chrome
headless=true
build=1.2.45

Файл можно положить руками или сформировать автоматически. Пример фикстуры, которая создаёт его перед запуском:

import os, pathlib, pytest

@pytest.fixture(scope="session", autouse=True)
def allure_env():
    out = pathlib.Path("allure-results")
    out.mkdir(exist_ok=True)
    props = out / "environment.properties"
    props.write_text(
        f"env={os.getenv('ENV','local')}\n"
        f"browser={os.getenv('BROWSER','chrome')}\n"
        f"headless={os.getenv('HEADLESS','true')}\n",
        encoding="utf-8"
    )

После сборки отчёта параметры отображаются в интерфейсе Allure.

Параллельные прогоны и истории

Если тесты в CI запускаются параллельно, результаты можно собрать в одну папку и потом сделать общий отчёт. Каждый джоб выгружает allure-results, а перед генерацией HTML эти папки объединяются. В итоге отчёт получается один.

Чтобы появились графики и история, Allure читает папку history. Её можно брать из предыдущего отчёта. Достаточно сохранять allure-report/history как артефакт и копировать его в allure-results перед сборкой. Тогда новый отчёт покажет тренды и повторяющиеся падения.

Генерация отчётов с шагами, скриншотами и логами

Allure собирает «сырые» результаты во время прогона Pytest и превращает их в отчёт со структурой сценария. Чтобы в отчёте появились шаги, в тестах используют контекстный менеджер allure.step. Каждый такой блок виден как отдельная строка сценария с временем и статусом. Простейший тест фиксирует три шага: открытие страницы, ввод данных и проверка результата.

## tests/test_login_allure.py
import allure
from selenium.webdriver.common.by import By

def test_login_success(driver):
    with allure.step("Открыть страницу логина"):
        driver.get("https://the-internet.herokuapp.com/login")

    with allure.step("Ввести валидные логин и пароль"):
        driver.find_element(By.ID, "username").send_keys("tomsmith")
        driver.find_element(By.ID, "password").send_keys("SuperSecretPassword!")
        driver.find_element(By.CSS_SELECTOR, "button[type='submit']").click()

    with allure.step("Убедиться, что произошло перенаправление в защищённую зону"):
        assert "/secure" in driver.current_url

Скриншоты и DOM удобнее прикладывать автоматически при падении. Это делает хук pytest_runtest_makereport: он срабатывает после вызова теста и видит, завершился ли он неуспешно. Если да, драйвер делает снимок экрана, HTML сохраняется целиком, а для Chrome дополнительно собираются логи консоли. Такой подход избавляет от копипасты try/except в каждом тесте.

## tests/conftest.py
import pytest, allure

@pytest.hookimpl(hookwrapper=True)
def pytest_runtest_makereport(item, call):
    outcome = yield
    rep = outcome.get_result()
    if rep.when != "call" or not rep.failed:
        return
    driver = item.funcargs.get("driver")
    if not driver:
        return
    try:
        allure.attach(
            driver.get_screenshot_as_png(),
            name="screen",
            attachment_type=allure.attachment_type.PNG
        )
    except Exception:
        pass
    try:
        allure.attach(
            driver.page_source,
            name="dom",
            attachment_type=allure.attachment_type.HTML
        )
    except Exception:
        pass
    try:
        logs = driver.get_log("browser")
        text = "\n".join(f"{e['level']} {e['message']}" for e in logs) or "browser log is empty"
        allure.attach(text, name="browser.log", attachment_type=allure.attachment_type.TEXT)
    except Exception:
        ## не все драйверы и режимы headless поддерживают get_log("browser")
        pass

Если проект использует Page Object, удобно прикладывать артефакты в самих методах шагов. Базовая страница оборачивает ключевые действия в шаги Allure и умеет добавлять скриншот на критичных операциях. Так в отчёте видна «дорожка» из кликов и вводов, а на неуспехе остаётся состояние экрана именно в нужный момент.

## pages/base_page.py
import allure
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC

class BasePage:
    def __init__(self, driver, timeout=10):
        self.driver = driver
        self.wait = WebDriverWait(driver, timeout)

    def open(self, url):
        with allure.step(f"Открыть {url}"):
            self.driver.get(url)

    def type(self, locator, text):
        with allure.step(f"Ввести '{text}' в {locator}"):
            el = self.wait.until(EC.visibility_of_element_located(locator))
            el.clear(); el.send_keys(text)

    def click(self, locator):
        with allure.step(f"Клик по {locator}"):
            el = self.wait.until(EC.element_to_be_clickable(locator))
            el.click()

Для ручного вложения артефактов в конкретном тесте достаточно вызвать allure.attach. Это полезно, когда нужно приложить бизнес-данные шага: JSON ответа API, расчётные параметры, текст всплывающего сообщения.

import json, allure

def attach_json(name, obj):
    allure.attach(
        json.dumps(obj, ensure_ascii=False, indent=2),
        name=name,
        attachment_type=allure.attachment_type.JSON
    )

Иногда требуется всегда сохранять «чистовые» артефакты успешных кейсов, например, скриншот после покупки или финальный DOM формы. В таких точках вложение делается прямо в тесте или методе страницы, независимо от статуса. Это не стоит применять повсеместно, чтобы не раздувать отчёт, но для контрольных шагов это оправдано.

with allure.step("Проверка сообщения об успехе"):
    msg = page.message()
    allure.attach(msg, name="flash.txt", attachment_type=allure.attachment_type.TEXT)
    assert "You logged into a secure area!" in msg

Чтобы отчёт группировался по функциям системы, тесты размечаются фичами, историями и критичностью. Эти атрибуты читаются прямо в интерфейсе Allure и помогают фильтровать сценарии. Разметку удобно ставить на уровне тестов или модулей, а общие теги можно подвесить через Pytest-марки и сконвертировать в Allure в фикстуре, если команда так договорилась.

import allure, pytest

@allure.feature("Авторизация")
@allure.story("Успешный вход")
@allure.severity(allure.severity_level.CRITICAL)
@pytest.mark.auth
def test_login_success(driver):
    ...

Генерация отчёта состоит из двух команд: Pytest записывает результаты с --alluredir, утилита allure собирает HTML. Каталоги для «сырья» и отчёта не пересекаются.

uv run pytest -vv --alluredir=allure-results
allure generate allure-results -o allure-report --clean

Такой набор даёт полный след прогона: шаги видны по порядку, скриншоты и DOM приложены на падениях, логи консоли помогают отличить баг фронтенда от ошибки теста. В результате воспроизведение проблемы занимает меньше времени, а сами тесты остаются чистыми, потому что вся рутинная сборка артефактов спрятана в хуке и базовых методах.

Интеграция с GitHub Actions

Цель пайплайна проста: установить ��ависимости, запустить Pytest с --alluredir, сохранить «сырые» результаты и собрать HTML-отчёт. В рабочем варианте первый джоб прогоняет тесты и выгружает allure-results как артефакт, второй джоб генерирует HTML в allure-report и тоже сохраняет его как артефакт.

## .github/workflows/tests.yml
name: tests
on: [push, pull_request]

jobs:
  run-tests:
    runs-on: ubuntu-latest
    timeout-minutes: 30
    steps:
      - uses: actions/checkout@v5
      - uses: actions/setup-python@v6
        with:
          python-version: '3.14'
      - name: Install deps
        run: |
          pip install uv
          uv sync --frozen
      - name: Run pytest with Allure
        env:
          BASE_URL: https://the-internet.herokuapp.com
          HEADLESS: "true"
        run: |
          uv run pytest -vv --tb=auto --alluredir=allure-results
      - name: Upload raw allure results
        uses: actions/upload-artifact@v4
        with:
          name: allure-results
          path: allure-results

  build-report:
    needs: run-tests
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v5
      - name: Download raw results
        uses: actions/download-artifact@v4
        with:
          name: allure-results
          path: allure-results
      - name: Generate HTML report
        uses: docker://frankescobar/allure-docker-service:latest
        with:
          args: bash -lc "allure generate allure-results -o allure-report --clean"
      - name: Upload HTML report
        uses: actions/upload-artifact@v4
        with:
          name: allure-report
          path: allure-report

Матрица браузеров позволяет прогнать один и тот же набор в нескольких средах. Каждая ячейка матрицы пишет свой allure-results, а второй джоб объединяет их перед сборкой отчёта.

run-tests:
    strategy:
      fail-fast: false
      matrix:
        browser: [chrome, firefox]
    steps:
      ## ...
      - name: Run pytest
        run: uv run pytest -vv --alluredir=allure-results/${{ matrix.browser }} --browser=${{ matrix.browser }}
      - name: Upload raw
        uses: actions/upload-artifact@v4
        with:
          name: allure-results-${{ matrix.browser }}
          path: allure-results/${{ matrix.browser }}

  build-report:
    steps:
      ## ...
      - uses: actions/download-artifact@v4
        with: { name: allure-results-chrome, path: allure-results/chrome }
      - uses: actions/download-artifact@v4
        with: { name: allure-results-firefox, path: allure-results/firefox }
      - name: Merge results and build
        uses: docker://frankescobar/allure-docker-service:latest
        with:
          args: |
            bash -lc "mkdir -p merged && cp -r allure-results/*/* merged/ && \
                      allure generate merged -o allure-report --clean"

Публикация отчёта на GitHub Pages делается отдельным шагом. Генерация HTML остаётся в CI, после чего содержимое папки allure-report деплоится в ветку gh-pages.

- name: Deploy report to Pages
  uses: peaceiris/actions-gh-pages@v4
  with:
    github_token: ${{ secrets.GITHUB_TOKEN }}
    publish_dir: allure-report
    publish_branch: gh-pages

Для Selenium Grid или Selenoid достаточно прокинуть переменные и использовать удалённый command_executor. Агент Actions не меняется, тесты подключаются к гриду по URL.

- name: Run pytest against Grid
   env:
     SELENIUM_REMOTE_URL: http://grid.internal:4444/wd/hub
   run: uv run pytest --alluredir=allure-results

Интеграция с Jenkins

Jenkins решает ту же задачу стадиями пайплайна. Удобно использовать виртуальное окружение, сохранять allure-results как артефакт и публиковать отчёт через плагин Allure.

// Jenkinsfile (Declarative Pipeline)
pipeline {
  agent any
  options { timestamps(); ansiColor('xterm'); buildDiscarder(logRotator(numToKeepStr: '20')) }
  environment {
    BASE_URL = 'https://the-internet.herokuapp.com'
    HEADLESS = 'true'
    VENV = '.venv'
  }
  stages {
    stage('Checkout') {
      steps { checkout scm }
    }
    stage('Setup Python') {
      steps {
        sh '''
          pip install uv
          uv sync --python 3.14 --frozen
        '''
      }
    }
    stage('Pytest') {
      steps {
        sh '''
          uv run pytest -vv --tb=auto --alluredir=allure-results
        '''
      }
      post {
        always {
          archiveArtifacts artifacts: 'allure-results/**', allowEmptyArchive: true
          junit allowEmptyResults: true, testResults: '**/junit*.xml'
        }
      }
    }
    stage('Allure Report') {
      steps {
        allure includeProperties: false, jdk: '', results: [[path: 'allure-results']]
      }
    }
  }
  post {
    always {
      cleanWs(deleteDirs: true, notFailBuild: true)
    }
  }
}

Если в пайплайне параллельные ветки, каждая ветка складывает свои allure-results в уникальную папку и делает stash. Финальная стадия Allure Report делает unstash всех наборов в общий каталог и публикует один отчёт.

stage('Matrix') {
  parallel {
    stage('Chrome') {
      steps {
        sh 'uv run pytest --browser=chrome --alluredir=allure-results/chrome'
        stash name: 'allure-chrome', includes: 'allure-results/chrome/**'
      }
    }
    stage('Firefox') {
      steps {
        sh 'uv run pytest --browser=firefox --alluredir=allure-results/firefox'
        stash name: 'allure-firefox', includes: 'allure-results/firefox/**'
      }
    }
  }
}
stage('Allure Report') {
  steps {
    dir('merged') {
      unstash 'allure-chrome'
      unstash 'allure-firefox'
      sh 'mkdir -p results && cp -r ../allure-results/*/* results/ || true'
    }
    allure results: [[path: 'merged/results']]
  }
}

Подключение удалённого Selenium в Jenkins не отличается от локального запуска. Драйвер создаётся с command_executor из переменной окружения, а ноды Jenkins могут одновременно держать несколько браузеров, если хватает ресурсов.

environment {
  SELENIUM_REMOTE_URL = 'http://selenium-grid:4444/wd/hub'
}

Отдача статического HTML-отчёта без плагина делается через publishHTML. Этот вариант пригодится в минимальной инсталляции, где плагин Allure недоступен.

stage('Build HTML') {
  steps {
    sh '. ${VENV}/bin/activate && allure generate allure-results -o allure-report --clean'
    publishHTML(target: [
      reportDir: 'allure-report',
      reportFiles: 'index.html',
      reportName: 'Allure Report',
      keepAll: true, alwaysLinkToLastBuild: true
    ])
  }
}

Для стабильности в обоих CI удобно фиксировать одинаковый размер окна, headless-режим и уникальные каталоги артефактов в фикстуре driver. Тогда локальный запуск и агент в пайплайне ведут себя одинаково, а отчёты всегда содержат шаги, скриншоты, DOM и логи браузера из хуков Pytest.

Хранение и просмотр Allure-отчётов в CI

Allure формирует два вида данных: сырые результаты и HTML-отчёт.

В CI сохраняют оба: результаты нужны для пересборки и объединения параллельных прогонов, HTML — для просмотра.

Правильная схема такая: на этапе тестов Pytest пишет результаты в каталог, CI сохраняет его как артефакт, затем отдельный шаг собирает HTML из этого каталога и публикует статический сайт или прикрепляет архив отчёта к сборке.

Рекомендуемые программы

+7 800 100 22 47

бесплатно по РФ

+7 495 085 21 62

бесплатно по Москве

108813 г. Москва, вн.тер.г. поселение Московский,
г. Московский, ул. Солнечная, д. 3А, стр. 1, помещ. 20Б/3
ОГРН 1217300010476
ИНН 7325174845