509 lines
24 KiB
Markdown
509 lines
24 KiB
Markdown
# e2e_Pets — End-to-End тесты портала учёта животных
|
||
|
||
Автоматизированное тестирование портала [vet.goznak.ru](https://vet.goznak.ru)
|
||
на базе **Playwright + pytest**.
|
||
|
||
---
|
||
|
||
## Содержание
|
||
|
||
- [Структура проекта](#структура-проекта)
|
||
- [Быстрый старт](#быстрый-старт)
|
||
- [Запуск тестов](#запуск-тестов)
|
||
- [Поддержка Стандартного и ГОСТ TLS](#поддержка-стандартного-и-гост-tls)
|
||
- [Создание нового теста](#создание-нового-теста)
|
||
- [Добавление генератора данных (ФЛК)](#добавление-генератора-данных-флк)
|
||
- [Добавление статического артефакта](#добавление-статического-артефакта)
|
||
- [Создание теста через визуальный рекордер (Codegen)](#создание-теста-через-визуальный-рекордер-codegen)
|
||
|
||
---
|
||
|
||
## Структура проекта
|
||
|
||
```
|
||
e2e_Pets/
|
||
├── .env # Учётные данные и настройки среды (не в git)
|
||
├── .env.example # Шаблон .env
|
||
├── conftest.py # Корневой conftest (CLI-флаги, HTML-отчёт и метаданные)
|
||
├── pytest.ini # Конфигурация pytest
|
||
├── requirements.txt # Зависимости
|
||
│
|
||
└── tests/
|
||
├── conftest.py # Глобальные фикстуры (browser_page, bp_state, _auto_browser)
|
||
├── testdata/
|
||
│ └── generators.py # Генераторы случайных данных по ФЛК
|
||
├── utils/
|
||
│ ├── tls.py # Детектор TLS и адаптивный менеджер браузеров
|
||
│ └── test_tls.py # Модульные тесты механизмов TLS
|
||
│
|
||
├── 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. Клонировать репозиторий
|
||
|
||
```powershell
|
||
git clone <url>
|
||
cd e2e_Pets
|
||
```
|
||
|
||
### 2. Создать виртуальное окружение и установить зависимости
|
||
|
||
```powershell
|
||
python -m venv venv
|
||
.\venv\Scripts\pip.exe install -r requirements.txt
|
||
.\venv\Scripts\playwright.exe install chromium
|
||
```
|
||
|
||
### 3. Настроить конфигурацию и учётные данные
|
||
|
||
```powershell
|
||
Copy-Item .env.example .env
|
||
# Открыть .env и указать BASE_URL, логины и пароли ролей
|
||
```
|
||
|
||
### 4. (Опционально) Настройка ГОСТ-браузера
|
||
|
||
Если целевой портал защищён ГОСТ TLS (ГОСТ Р 34.12-2015, 34.10-2012) или вы хотите тестировать ГОСТ-контур:
|
||
1. Скачайте архив **[Chromium-GOST](https://github.com/deemru/chromium-gost/releases)** (например, `chromium-gost-...-windows-amd64.zip`).
|
||
2. Распакуйте его в стандартный каталог `%LOCALAPPDATA%\Chromium-Gost` (или `C:\Program Files\Chromium-Gost`). Механизм тестов обнаружит его **автоматически**.
|
||
3. *Альтернатива:* если браузер распакован в произвольную папку, укажите путь в `.env`:
|
||
```ini
|
||
CHROMIUM_GOST_PATH=C:\MyPrograms\chromium-gost\chrome.exe
|
||
```
|
||
4. Убедитесь, что на компьютере установлен криптопровайдер с ГОСТ-алгоритмами (КриптоПро CSP, Security Code CSP / Континент-АП).
|
||
|
||
---
|
||
|
||
## Запуск тестов
|
||
|
||
> Все команды выполняются из корня проекта (`e2e_Pets/`).
|
||
> HTML-отчёт со статусом, типом TLS и запущенным браузером автоматически формируется в `html-report/report.html`.
|
||
|
||
---
|
||
|
||
### Автоматический запуск (рекомендуемый)
|
||
|
||
По умолчанию включён режим `--tls-mode=auto`. Система перед стартом проверяет сокет целевого `BASE_URL`:
|
||
- Если сайт доступен по **стандартному TLS** → автоматически запускается встроенный **Chromium Playwright**.
|
||
- Если сайт защищён **ГОСТ TLS** → автоматически запускается **Chromium-GOST**.
|
||
|
||
```powershell
|
||
# Запуск всех тестов в авто-режиме
|
||
.\venv\Scripts\pytest.exe
|
||
```
|
||
|
||
---
|
||
|
||
### Принудительный выбор режима TLS
|
||
|
||
Если вы хотите явно зафиксировать браузер в обход автоопределения:
|
||
|
||
```powershell
|
||
# Принудительно запустить через Chromium-GOST
|
||
.\venv\Scripts\pytest.exe --tls-mode=gost
|
||
|
||
# Принудительно запустить через стандартный Chromium Playwright
|
||
.\venv\Scripts\pytest.exe --tls-mode=standard
|
||
```
|
||
|
||
---
|
||
|
||
### Запуск тестов конкретного ЛК (роли)
|
||
|
||
```powershell
|
||
# ЛК Управление ветеринарии (все бизнес-процессы)
|
||
.\venv\Scripts\pytest.exe tests/01_vetdept/
|
||
|
||
# ЛК Ветеринарная станция (все бизнес-процессы)
|
||
.\venv\Scripts\pytest.exe tests/02_vetstation/
|
||
```
|
||
|
||
---
|
||
|
||
### Запуск конкретного бизнес-процесса или шага
|
||
|
||
```powershell
|
||
# Запуск BP01 в ЛК Управление ветеринарии
|
||
.\venv\Scripts\pytest.exe tests/01_vetdept/BP01_navigation/
|
||
|
||
# Запуск только шага 1 (Dashboard)
|
||
.\venv\Scripts\pytest.exe tests/01_vetdept/BP01_navigation/ -k "s01"
|
||
|
||
# Запуск шагов 1 и 2
|
||
.\venv\Scripts\pytest.exe tests/01_vetdept/BP01_navigation/ -k "s01 or s02"
|
||
```
|
||
|
||
---
|
||
|
||
### Полезные флаги запуска
|
||
|
||
| Флаг | Описание |
|
||
|---|---|
|
||
| `-v` | Подробный вывод с отображением docstring каждого шага |
|
||
| `--tls-mode=auto` | Режим TLS: `auto` (по умолчанию), `gost` или `standard` |
|
||
| `--ignore-https-errors` | Игнорировать ошибки сертификатов SSL (по умолчанию включено) |
|
||
| `-k "BP01"` | Фильтр тестов по подстроке в имени класса или метода |
|
||
| `--co -q` | Только показать список тестов без их выполнения |
|
||
| `--slowmo 500` | Замедление действий в браузере (в мс, по умолчанию 1000мс в `pytest.ini`) |
|
||
| `-o addopts=""` | Сбросить дефолтные флаги `pytest.ini` (например, для headless-запуска в CI) |
|
||
|
||
```powershell
|
||
# Просмотр дерева тестов без запуска браузера
|
||
.\venv\Scripts\pytest.exe --co -q
|
||
|
||
# Быстрый запуск без задержки slowmo
|
||
.\venv\Scripts\pytest.exe tests/01_vetdept/ --slowmo 0
|
||
|
||
# Запуск в режиме ГОСТ с подробным выводом
|
||
.\venv\Scripts\pytest.exe tests/01_vetdept/ --tls-mode=gost -v
|
||
```
|
||
|
||
---
|
||
|
||
## Поддержка Стандартного и ГОСТ TLS
|
||
|
||
В проект встроен универсальный механизм запуска, который автоматически определяет, какой протокол шифрования используется на целевом портале (`BASE_URL`), и выбирает подходящий браузер.
|
||
|
||
### Как работает автоопределение:
|
||
1. **Двухфазная детекция**:
|
||
- Выполняется тестовое SSL-рукопожатие сокета.
|
||
- Если сайт использует стандартные алгоритмы (TLS 1.2 / TLS 1.3, AES, CHACHA20 и др.), даже при наличии самоподписанного или тестового корпоративного сертификата, система распознаёт его как **Стандартный TLS** и запускает встроенный **Chromium Playwright**.
|
||
- Если сервер требует российские криптографические алгоритмы ГОСТ (ГОСТ Р 34.12-2015 «Кузнечик»/«Магма», ГОСТ Р 34.10-2012, 28147-89), которые не поддерживаются штатным OpenSSL в Python, система идентифицирует **ГОСТ TLS**.
|
||
|
||
2. **Автопоиск браузера для ГОСТ**:
|
||
- Система автоматически сканирует стандартные каталоги установки:
|
||
- `%LOCALAPPDATA%\Chromium-Gost\Application\chrome.exe`
|
||
- `%PROGRAMFILES%\Chromium-Gost\chrome.exe` (и в `Program Files (x86)`)
|
||
- **Яндекс.Браузер** (при установленном КриптоПро CSP): `%LOCALAPPDATA%\Yandex\...`, `%PROGRAMFILES%\Yandex\...`
|
||
- Системный `PATH` (`chromium-gost`, `yandex-browser`).
|
||
- Если браузер найден в системе, тесты стартуют автоматически без необходимости вручную прописывать путь в `.env`.
|
||
|
||
3. **Варианты запуска под ГОСТ**:
|
||
- **Автозапуск (рекомендуется)**: Установить [Chromium-GOST](https://github.com/deemru/chromium-gost/releases) или Яндекс.Браузер с КриптоПро CSP. При нестандартном расположении указать в `.env`:
|
||
```ini
|
||
CHROMIUM_GOST_PATH=C:\CustomPath\chrome.exe
|
||
```
|
||
- **Подключение по CDP (если браузер уже запущен вручную)**:
|
||
```powershell
|
||
chrome.exe --remote-debugging-port=9222 --no-first-run
|
||
```
|
||
Тесты автоматически подключатся к открытому браузеру на порту `9222`.
|
||
|
||
4. **Управление режимом TLS**:
|
||
- Через аргумент CLI:
|
||
```powershell
|
||
.\venv\Scripts\pytest.exe --tls-mode=auto # Автоопределение (по умолчанию)
|
||
.\venv\Scripts\pytest.exe --tls-mode=gost # Принудительно ГОСТ
|
||
.\venv\Scripts\pytest.exe --tls-mode=standard # Принудительно Стандартный
|
||
```
|
||
- Либо через `.env`:
|
||
```ini
|
||
TLS_MODE=auto # auto | gost | standard
|
||
```
|
||
|
||
5. **Отображение в HTML-отчёте**:
|
||
- В блоке **Environment** сгенерированного отчёта `html-report/report.html` фиксируются:
|
||
- Реальный режим TLS и использованный шифр (например, `Стандартный TLS (TLSv1.3, TLS_AES_256_GCM_SHA384)`).
|
||
- Тип и путь фактически запущенного браузера (`Chromium (Playwright)` / `Chromium GOST` / `Яндекс.Браузер`).
|
||
|
||
---
|
||
|
||
## Создание нового теста
|
||
|
||
### Нужен новый бизнес-процесс?
|
||
|
||
Опиши его в свободной форме — например:
|
||
|
||
> *«Нужен тест для ветстанции: открыть список животных, применить фильтр
|
||
> по кошкам, убедиться что список отфильтровался, открыть карточку
|
||
> первого животного, проверить наличие кнопки редактирования»*
|
||
|
||
ИИ-ассистент создаст файл в нужном месте, следуя архитектуре проекта.
|
||
|
||
---
|
||
|
||
### Если хочешь создать вручную — шаблон:
|
||
|
||
#### 1. Создать папку бизнес-процесса
|
||
|
||
```powershell
|
||
# Для роли vetstation, новый бизнес-процесс BP02
|
||
mkdir tests\02_vetstation\BP02_animals
|
||
New-Item tests\02_vetstation\BP02_animals\__init__.py
|
||
```
|
||
|
||
#### 2. Создать файл теста
|
||
|
||
Имя файла: `test_bp02_<название>.py`
|
||
|
||
```python
|
||
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`](tests/testdata/generators.py)
|
||
|
||
Содержит пошаговое руководство по добавлению нового генератора прямо в начале файла. Коротко:
|
||
|
||
```python
|
||
# Шаблон новой функции-генератора:
|
||
|
||
def gen_<сущность>_<поле>() -> str:
|
||
"""
|
||
Краткое описание.
|
||
|
||
ФЛК:
|
||
- Правило 1
|
||
- Правило 2
|
||
|
||
Пример вывода: 'AB1234'
|
||
"""
|
||
# ... реализация ...
|
||
```
|
||
|
||
Использование в тесте:
|
||
|
||
```python
|
||
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 # передаём следующему шагу
|
||
```
|
||
|
||
---
|
||
|
||
## Добавление статического артефакта
|
||
|
||
1. Положи файл в папку `fixtures/` нужной роли:
|
||
```
|
||
tests/01_vetdept/fixtures/images/cat_photo.jpg
|
||
```
|
||
|
||
2. Раскомментируй шаблон фикстуры в `tests/<роль>/conftest.py`:
|
||
```python
|
||
@pytest.fixture(scope="session")
|
||
def cat_photo(fixtures_dir) -> Path:
|
||
"""Фото кошки для теста загрузки."""
|
||
return fixtures_dir / "images" / "cat_photo.jpg"
|
||
```
|
||
|
||
3. Используй в тесте:
|
||
```python
|
||
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-код, который воспроизводит твои действия.
|
||
|
||
### Запуск рекордера
|
||
|
||
#### Вариант 1: Универсальный запуск с поддержкой ГОСТ TLS (рекомендуемый)
|
||
Штатная команда `playwright codegen` не умеет работать со сторонними браузерами напрямую. Для записи тестов на сайтах с **ГОСТ TLS** (а также обычным TLS) используйте встроенный скрипт `codegen.py`:
|
||
|
||
```powershell
|
||
# Запуск для URL по умолчанию из .env (с автоопределением TLS и выбором браузера):
|
||
.\venv\Scripts\python.exe codegen.py
|
||
|
||
# Принудительный запуск в режиме ГОСТ TLS (через Chromium-GOST):
|
||
.\venv\Scripts\python.exe codegen.py --gost
|
||
|
||
# Запуск для произвольного адреса:
|
||
.\venv\Scripts\python.exe codegen.py https://vet.goznak.ru
|
||
```
|
||
|
||
#### Вариант 2: Штатный запуск (только для сайтов со стандартным TLS)
|
||
```powershell
|
||
# Открыть встроенный Chromium с рекордером
|
||
.\venv\Scripts\playwright.exe codegen https://vet.goznak.ru
|
||
|
||
# Открыть рекордер и сразу сохранить результат в файл
|
||
.\venv\Scripts\playwright.exe codegen https://vet.goznak.ru --output tests\codegen_draft.py
|
||
```
|
||
|
||
При запуске откроются два окна:
|
||
- **Браузер (Chromium-GOST или Playwright Chromium)** — кликайте, заполняйте формы и переходите по разделам.
|
||
- **Playwright Inspector** — окно записи, в котором в реальном времени формируется Python-код.
|
||
|
||
### Порядок работы
|
||
|
||
1. **Запусти рекордер** командой выше
|
||
2. **Авторизуйся** под нужной ролью (введи логин/пароль из `.env`)
|
||
3. **Выполни нужный бизнес-процесс** — все действия записываются автоматически
|
||
4. **Скопируй сгенерированный код** из Playwright Inspector
|
||
5. **Адаптируй код** под архитектуру проекта (см. ниже)
|
||
|
||
---
|
||
|
||
### Как адаптировать код из Codegen в архитектуру проекта
|
||
|
||
Codegen генерирует **плоский скрипт**, проект использует **класс с шагами**.
|
||
Ниже показано как преобразовать одно в другое.
|
||
|
||
#### До (вывод Codegen):
|
||
|
||
```python
|
||
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)
|
||
```
|
||
|
||
#### После (архитектура проекта):
|
||
|
||
```python
|
||
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 в отчёте |
|