Добавлен хук pytest_runtest_makereport в tests/conftest.py. Заменяет технический nodeid (путь к файлу::класс::метод[chromium]) на первую строку docstring тестового метода. Результат в отчёте: До: tests/01_vetdept/BP01_navigation/test_bp01_navigation.py::TestBP01Navigation::test_s01_open_dashboard[chromium] После: Шаг 1: Открыть главную страницу (Dashboard) Правило: что написано в docstring метода — то отображается в отчёте.
133 lines
5.6 KiB
Python
133 lines
5.6 KiB
Python
import os
|
||
# pyrefly: ignore [missing-import]
|
||
import pytest
|
||
# pyrefly: ignore [missing-import]
|
||
from dotenv import load_dotenv
|
||
# pyrefly: ignore [missing-import]
|
||
from playwright.sync_api import Page
|
||
|
||
# Загружаем переменные из .env (файл находится в корне проекта)
|
||
load_dotenv()
|
||
|
||
# Базовый URL портала — берётся из .env или дефолт
|
||
BASE_URL = os.getenv("BASE_URL", "https://vet.goznak.ru")
|
||
|
||
# Учётные данные для разных ролей — берутся из .env
|
||
CREDENTIALS = {
|
||
"vetdept": {
|
||
"login": os.getenv("VETDEPT_LOGIN"),
|
||
"password": os.getenv("VETDEPT_PASSWORD"),
|
||
},
|
||
"vetstation": {
|
||
"login": os.getenv("VETSTATION_LOGIN"),
|
||
"password": os.getenv("VETSTATION_PASSWORD"),
|
||
},
|
||
"prefecture": {
|
||
"login": os.getenv("PREFECTURE_LOGIN"),
|
||
"password": os.getenv("PREFECTURE_PASSWORD"),
|
||
},
|
||
}
|
||
|
||
|
||
def _get_role(request) -> str:
|
||
"""Определяет роль теста по pytest-маркеру."""
|
||
for marker in request.node.iter_markers():
|
||
if marker.name in CREDENTIALS:
|
||
return marker.name
|
||
pytest.fail("ОШИБКА: У теста нет маркера роли (например, @pytest.mark.vetdept)!")
|
||
|
||
|
||
def _login(page: Page, role: str) -> None:
|
||
"""Выполняет авторизацию на портале под указанной ролью."""
|
||
creds = CREDENTIALS[role]
|
||
if not creds["login"] or not creds["password"]:
|
||
pytest.fail(
|
||
f"ОШИБКА: Переменные окружения для роли '{role}' не заданы. "
|
||
f"Скопируй .env.example в .env и заполни данные."
|
||
)
|
||
print(f"\n[АВТОЛОГИН] Вход под ролью: {role}")
|
||
page.goto(BASE_URL)
|
||
page.get_by_role("textbox", name="Email").fill(creds["login"])
|
||
page.get_by_role("textbox", name="Введите пароль").fill(creds["password"])
|
||
page.get_by_role("button", name="Войти в аккаунт").click()
|
||
page.wait_for_selector("#app", timeout=10000)
|
||
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# Фикстура для бизнес-процессов (scope=class)
|
||
# Один браузер на весь класс — авторизация выполняется один раз.
|
||
# Создаёт собственный context и page, чтобы избежать ScopeMismatch
|
||
# с function-scoped page фикстурой pytest-playwright.
|
||
# ---------------------------------------------------------------------------
|
||
|
||
@pytest.fixture(scope="class")
|
||
def browser_page(browser, request):
|
||
"""
|
||
Авторизованная страница с жизненным циклом на весь класс (бизнес-процесс).
|
||
Все шаги внутри класса работают в одной вкладке браузера.
|
||
"""
|
||
role = _get_role(request)
|
||
context = browser.new_context()
|
||
page = context.new_page()
|
||
_login(page, role)
|
||
yield page
|
||
context.close()
|
||
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# Контейнер для передачи данных между шагами одного БП (scope=class)
|
||
# ---------------------------------------------------------------------------
|
||
|
||
@pytest.fixture(scope="class")
|
||
def bp_state() -> dict:
|
||
"""
|
||
Словарь для передачи данных между шагами одного бизнес-процесса.
|
||
Создаётся при старте класса, уничтожается после его завершения.
|
||
|
||
Пример:
|
||
def test_s01_create(self, browser_page, bp_state):
|
||
bp_state["claim_id"] = "12345"
|
||
|
||
def test_s02_verify(self, browser_page, bp_state):
|
||
assert bp_state["claim_id"] == "12345"
|
||
"""
|
||
return {}
|
||
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# Устаревшая фикстура — оставлена для обратной совместимости
|
||
# Используй browser_page для новых тестов
|
||
# ---------------------------------------------------------------------------
|
||
|
||
@pytest.fixture(scope="function")
|
||
def role_page(page: Page, request):
|
||
"""
|
||
[DEPRECATED] Авторизованная страница с жизненным циклом на один тест.
|
||
Для новых тестов используй browser_page (scope=class).
|
||
"""
|
||
role = _get_role(request)
|
||
_login(page, role)
|
||
yield page
|
||
page.context.clear_cookies()
|
||
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# Хук для отображения читаемых имён шагов в pytest-html отчёте
|
||
# ---------------------------------------------------------------------------
|
||
|
||
@pytest.hookimpl(hookwrapper=True)
|
||
def pytest_runtest_makereport(item, call):
|
||
"""
|
||
Заменяет технический путь к тесту в колонке Test отчёта pytest-html
|
||
на docstring метода (например, 'Шаг 1: Открыть главную страницу').
|
||
|
||
Если docstring отсутствует — оставляет оригинальный nodeid.
|
||
"""
|
||
outcome = yield
|
||
report = outcome.get_result()
|
||
|
||
# Берём docstring тестовой функции
|
||
doc = getattr(item.function, "__doc__", None)
|
||
if doc:
|
||
description = doc.strip().splitlines()[0] # только первая строка
|
||
report.nodeid = description |