conftest.py (корневой, новый):
- pytest_sessionstart — заменяет блок Environment:
Проект / Портал / Окружение / Браузер / Тестировщик
- pytest_html_report_title — заголовок 'Отчёт E2E тестирования'
- Использует config.stash[metadata_key] (правильный API
для pytest-metadata 3.x + pytest-html 4.x)
- Fallback на config._metadata для старых версий
tests/conftest.py:
- Убраны дублирующие хуки (перенесены в корневой conftest)
Переменные окружения для кастомизации:
TEST_ENV — название среды (по умолчанию 'Production')
TESTER_NAME — имя тестировщика (по умолчанию 'QA Team')
e2e_Pets — End-to-End тесты портала учёта животных
Автоматизированное тестирование портала vet.goznak.ru
на базе Playwright + pytest.
Содержание
- Структура проекта
- Быстрый старт
- Запуск тестов
- Создание нового теста
- Добавление генератора данных (ФЛК)
- Добавление статического артефакта
Структура проекта
e2e_Pets/
├── .env # Учётные данные (не в git)
├── .env.example # Шаблон .env
├── pytest.ini # Конфигурация pytest
├── requirements.txt # Зависимости
│
└── tests/
├── conftest.py # Глобальные фикстуры (browser_page, bp_state)
├── testdata/
│ └── generators.py # Генераторы случайных данных по ФЛК
│
├── 01_vetdept/ # ЛК: Управление ветеринарии
│ ├── conftest.py # fixtures_dir, shared_state
│ ├── fixtures/ # Статические артефакты (фото, PDF)
│ └── BP01_navigation/ # Бизнес-процесс 01
│ └── test_bp01_navigation.py
│
└── 02_vetstation/ # ЛК: Ветеринарная станция
├── conftest.py
├── fixtures/
└── BP01_dashboard/ # Бизнес-процесс 01
└── test_bp01_dashboard.py
Иерархия тестов: Проект → ЛК (роль) → БП (BP01_...) → Шаги (test_s01_..., test_s02_...)
Быстрый старт
1. Клонировать репозиторий
git clone <url>
cd e2e_Pets
2. Создать виртуальное окружение и установить зависимости
python -m venv venv
.\venv\Scripts\pip.exe install -r requirements.txt
.\venv\Scripts\playwright.exe install chromium
3. Настроить учётные данные
Copy-Item .env.example .env
# Открыть .env и заполнить логины/пароли
Запуск тестов
Все команды выполняются из корня проекта (
e2e_Pets/). HTML-отчёт автоматически создаётся вhtml-report/report.html.
Запустить ВСЕ тесты
.\venv\Scripts\pytest.exe
Запустить тесты конкретного ЛК (роли)
# ЛК Управление ветеринарии
.\venv\Scripts\pytest.exe tests/01_vetdept/
# ЛК Ветеринарная станция
.\venv\Scripts\pytest.exe tests/02_vetstation/
Запустить конкретный бизнес-процесс
# BP01 навигации в ЛК Управление ветеринарии
.\venv\Scripts\pytest.exe tests/01_vetdept/BP01_navigation/
# BP01 дашборда в ЛК Ветеринарная станция
.\venv\Scripts\pytest.exe tests/02_vetstation/BP01_dashboard/
Запустить конкретный шаг
# Только шаг 3 из BP01 ветстанции
.\venv\Scripts\pytest.exe tests/02_vetstation/BP01_dashboard/ -k "s03"
# Только шаги 1 и 2
.\venv\Scripts\pytest.exe tests/02_vetstation/BP01_dashboard/ -k "s01 or s02"
Полезные флаги
| Флаг | Описание |
|---|---|
-v |
Подробный вывод с именами шагов |
--headed |
Показывать браузер (по умолчанию включён в pytest.ini) |
--headless |
Запустить без браузера (быстрее) |
--slowmo 500 |
Замедление действий на 500мс (для отладки) |
-k "BP01" |
Запустить только тесты содержащие "BP01" в имени |
--co -q |
Только показать список тестов без запуска |
# Показать список всех тестов (без запуска)
.\venv\Scripts\pytest.exe --co -q
# Запустить без браузера и без замедления
.\venv\Scripts\pytest.exe --headless --slowmo 0
# Запустить в headless-режиме только vetdept
.\venv\Scripts\pytest.exe tests/01_vetdept/ --headless --slowmo 0
Создание нового теста
Нужен новый бизнес-процесс?
Опиши его в свободной форме — например:
«Нужен тест для ветстанции: открыть список животных, применить фильтр по кошкам, убедиться что список отфильтровался, открыть карточку первого животного, проверить наличие кнопки редактирования»
ИИ-ассистент создаст файл в нужном месте, следуя архитектуре проекта.
Если хочешь создать вручную — шаблон:
1. Создать папку бизнес-процесса
# Для роли vetstation, новый бизнес-процесс BP02
mkdir tests\02_vetstation\BP02_animals
New-Item tests\02_vetstation\BP02_animals\__init__.py
2. Создать файл теста
Имя файла: test_bp02_<название>.py
import os
import pytest
from playwright.sync_api import Page, expect
BASE_URL = os.getenv("BASE_URL", "https://vet.goznak.ru")
@pytest.mark.vetstation # <- маркер роли
@pytest.mark.usefixtures("browser_page") # <- авторизация и браузер
class TestBP02Animals:
"""
BP02: Краткое описание бизнес-процесса
Проверяет что пользователь может:
- Шаг 1: ...
- Шаг 2: ...
"""
@pytest.mark.dependency(name="BP02::s01")
def test_s01_<название>(self, browser_page: Page, bp_state: dict):
"""Шаг 1: Описание шага — именно это появится в отчёте"""
# ... код теста ...
bp_state["key"] = "value" # передать данные следующему шагу
@pytest.mark.dependency(name="BP02::s02", depends=["BP02::s01"])
def test_s02_<название>(self, browser_page: Page, bp_state: dict):
"""Шаг 2: Описание шага"""
value = bp_state["key"] # получить данные от предыдущего шага
# ... код теста ...
3. Правила именования
| Элемент | Формат | Пример |
|---|---|---|
| Папка БП | BP<NN>_<описание> |
BP02_animals |
| Файл теста | test_bp<NN>_<описание>.py |
test_bp02_animals.py |
| Класс | TestBP<NN><Описание> |
TestBP02Animals |
| Метод (шаг) | test_s<NN>_<описание> |
test_s01_open_list |
| Зависимость | BP<NN>::s<NN> |
BP02::s01 |
4. Маркеры ролей
| Маркер | Роль |
|---|---|
@pytest.mark.vetdept |
Управление ветеринарии |
@pytest.mark.vetstation |
Ветеринарная станция |
@pytest.mark.prefecture |
Администрация (префектура) |
Добавление генератора данных (ФЛК)
Файл: tests/testdata/generators.py
Содержит пошаговое руководство по добавлению нового генератора прямо в начале файла. Коротко:
# Шаблон новой функции-генератора:
def gen_<сущность>_<поле>() -> str:
"""
Краткое описание.
ФЛК:
- Правило 1
- Правило 2
Пример вывода: 'AB1234'
"""
# ... реализация ...
Использование в тесте:
from tests.testdata.generators import gen_microchip, gen_animal_name
def test_s01_fill_form(self, browser_page, bp_state):
name = gen_animal_name()
chip = gen_microchip()
browser_page.fill("#name", name)
browser_page.fill("#chip", chip)
bp_state["name"] = name # передаём следующему шагу
Добавление статического артефакта
-
Положи файл в папку
fixtures/нужной роли:tests/01_vetdept/fixtures/images/cat_photo.jpg -
Раскомментируй шаблон фикстуры в
tests/<роль>/conftest.py:@pytest.fixture(scope="session") def cat_photo(fixtures_dir) -> Path: """Фото кошки для теста загрузки.""" return fixtures_dir / "images" / "cat_photo.jpg" -
Используй в тесте:
def test_s02_upload(self, browser_page, bp_state, cat_photo): browser_page.set_input_files("input[type=file]", str(cat_photo))
Создание теста через визуальный рекордер (Codegen)
Playwright Codegen — встроенный инструмент записи действий в браузере. Ты кликаешь, вводишь текст и переходишь по страницам — Codegen генерирует Python-код, который воспроизводит твои действия.
Запуск рекордера
# Открыть браузер с рекордером (без авторизации)
.\venv\Scripts\playwright.exe codegen https://vet.goznak.ru
# Открыть браузер с рекордером и сохранить результат в файл
.\venv\Scripts\playwright.exe codegen https://vet.goznak.ru --output tests\codegen_draft.py
Откроются два окна:
- Браузер — управляй им как обычно: кликай, заполняй формы, переходи по разделам
- Playwright Inspector — в реальном времени показывает сгенерированный код
Порядок работы
- Запусти рекордер командой выше
- Авторизуйся под нужной ролью (введи логин/пароль из
.env) - Выполни нужный бизнес-процесс — все действия записываются автоматически
- Скопируй сгенерированный код из Playwright Inspector
- Адаптируй код под архитектуру проекта (см. ниже)
Как адаптировать код из Codegen в архитектуру проекта
Codegen генерирует плоский скрипт, проект использует класс с шагами.
Ниже показано как преобразовать одно в другое.
До (вывод Codegen):
from playwright.sync_api import Playwright, sync_playwright, expect
def run(playwright: Playwright) -> None:
browser = playwright.chromium.launch(headless=False)
context = browser.new_context()
page = context.new_page()
page.goto("https://vet.goznak.ru/auth")
page.get_by_role("textbox", name="Email").fill("gz_vetstation@vet.goznak.ru")
page.get_by_role("textbox", name="Введите пароль").fill("A6wrpzpD5T")
page.get_by_role("button", name="Войти в аккаунт").click()
page.get_by_role("link", name="Животные").click()
expect(page).to_have_url("https://vet.goznak.ru/animals")
page.get_by_role("button", name="Добавить животное").click()
page.get_by_label("Кличка").fill("Мурка")
page.get_by_role("button", name="Сохранить").click()
expect(page.get_by_text("Мурка")).to_be_visible()
context.close()
browser.close()
with sync_playwright() as playwright:
run(playwright)
После (архитектура проекта):
import os
import pytest
from playwright.sync_api import Page, expect
BASE_URL = os.getenv("BASE_URL", "https://vet.goznak.ru")
@pytest.mark.vetstation
@pytest.mark.usefixtures("browser_page")
class TestBP02AddAnimal:
"""
BP02: Добавление нового животного
Проверяет что пользователь может:
- Перейти в раздел Животные
- Открыть форму добавления
- Заполнить кличку и сохранить
- Убедиться что животное появилось в списке
"""
# Авторизация и логин/пароль — убраны полностью:
# этим занимается фикстура browser_page (см. tests/conftest.py)
@pytest.mark.dependency(name="BP02::s01")
def test_s01_go_to_animals(self, browser_page: Page, bp_state: dict):
"""Шаг 1: Перейти в раздел Животные"""
browser_page.get_by_role("link", name="Животные").click()
expect(browser_page).to_have_url(BASE_URL + "/animals", timeout=10000)
@pytest.mark.dependency(name="BP02::s02", depends=["BP02::s01"])
def test_s02_open_add_form(self, browser_page: Page, bp_state: dict):
"""Шаг 2: Открыть форму добавления животного"""
browser_page.get_by_role("button", name="Добавить животное").click()
expect(browser_page.get_by_label("Кличка")).to_be_visible(timeout=10000)
@pytest.mark.dependency(name="BP02::s03", depends=["BP02::s02"])
def test_s03_fill_and_save(self, browser_page: Page, bp_state: dict):
"""Шаг 3: Заполнить кличку и сохранить"""
from tests.testdata.generators import gen_animal_name
name = gen_animal_name() # случайное имя по ФЛК
browser_page.get_by_label("Кличка").fill(name)
browser_page.get_by_role("button", name="Сохранить").click()
bp_state["animal_name"] = name # передаём шагу 4
@pytest.mark.dependency(name="BP02::s04", depends=["BP02::s03"])
def test_s04_verify_in_list(self, browser_page: Page, bp_state: dict):
"""Шаг 4: Проверить что животное появилось в списке"""
expect(
browser_page.get_by_text(bp_state["animal_name"])
).to_be_visible(timeout=10000)
Что изменилось при адаптации
| Было в Codegen | Стало в проекте |
|---|---|
browser.launch() + context.new_page() |
Фикстура browser_page (авто) |
| Явный логин/пароль в коде | Берётся из .env через фикстуру |
Один плоский def run() |
Класс с методами-шагами |
| Нет зависимостей между шагами | @pytest.mark.dependency(depends=[...]) |
Хардкод данных ("Мурка") |
Генераторы ФЛК (gen_animal_name()) |
| Нет передачи данных между шагами | bp_state["key"] = value |
| Нет отображения в отчёте | Docstring шага → колонка Test в отчёте |