Автотестирование UI: Pytest + Selenium + Page Object Model (POM)

Курс посвящён построению поддерживаемых UI-автотестов на Python с использованием Pytest, Selenium WebDriver и паттерна Page Object Model. Вы научитесь организовывать проект, писать стабильные тесты, работать с ожиданиями, фикстурами и формировать отчётность.

1. Введение: стек Pytest, Selenium и POM, архитектура проекта

Введение: стек Pytest, Selenium и POM, архитектура проекта

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

  • Pytest отвечает за запуск тестов, фикстуры, отчёты о падениях и организацию тестового набора.
  • Selenium WebDriver управляет браузером и позволяет взаимодействовать со страницами.
  • Page Object Model (POM) задаёт архитектуру, чтобы тесты были читаемыми и поддерживаемыми.
  • Цель этой статьи — понять роли инструментов, договориться о терминах и зафиксировать базовую структуру проекта, от которой будем отталкиваться дальше.

    Что делает каждый компонент стека

    Pytest

    Pytest — тестовый фреймворк для Python.

    Ключевые возможности, которые нам важны:

  • Автообнаружение тестов по именам файлов и функций.
  • Фикстуры: удобный способ создавать и закрывать ресурсы (например, браузер).
  • Параметризация: запуск одного теста с разными данными.
  • Маркировки: группировка тестов (например, smoke, regression).
  • Термины:

  • Тест — функция, которая выполняет проверку и падает, если ожидания не совпали с реальностью.
  • Фикстура — функция, которая подготавливает состояние для теста и возвращает нужный объект (часто WebDriver).
  • conftest.py — специальный файл Pytest, где обычно лежат общие фикстуры проекта.
  • Selenium WebDriver

    Selenium WebDriver — библиотека для управления браузером.

    Что делает Selenium в UI-тестах:

  • Открывает страницы по URL.
  • Находит элементы (кнопки, поля ввода, ссылки) и взаимодействует с ними.
  • Читает состояния: текст, атрибуты, видимость, доступность.
  • Ждёт нужных условий через ожидания.
  • Важно различать:

  • Локатор — способ найти элемент (например, CSS-селектор или XPath).
  • Явное ожидание — ожидание конкретного условия (элемент кликабелен, текст появился). На практике это надёжнее, чем sleep.
  • Документация по ожиданиям:

  • Selenium waits
  • Page Object Model (POM)

    POM — архитектурный подход, который отделяет как устроена страница от что именно проверяет тест.

    Идея простая:

  • В page object мы храним локаторы и методы действий на странице.
  • В тестах мы описываем сценарии на уровне бизнес-действий, не погружаясь в детали селекторов.
  • Классическое описание паттерна:

  • PageObject (Martin Fowler)
  • Как части стека связаны

  • Pytest запускает тесты и управляет жизненным циклом.
  • Selenium управляет браузером.
  • POM превращает работу с Selenium в понятный слой “страниц” и “действий”, чтобы тесты не превращались в набор CSS/XPath.
  • Почему POM критичен для поддержки

    Без POM тесты часто выглядят так:

  • Много повторяющегося кода поиска элементов.
  • Селекторы размазаны по всему проекту.
  • Любое изменение верстки ломает десятки тестов.
  • С POM подход меняется:

  • Селекторы живут в одном месте — в классе страницы.
  • Тесты читаются как сценарии: открой страницу → залогинься → проверь, что видишь имя пользователя.
  • При изменении UI чаще всего правится только соответствующий page object.
  • Базовая архитектура проекта

    Ниже — минимально жизнеспособная структура, удобная для роста.

    !Схема связей: тесты → page objects → WebDriver

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

    Что в чём хранить:

  • tests/ — только тестовые сценарии и проверки.
  • pages/ — Page Object классы: локаторы и методы действий.
  • fixtures/ — фикстуры Pytest (часто можно хранить и в conftest.py, но вынесение помогает не раздувать файл).
  • config/ — настройки окружений: базовый URL, таймауты, браузер.
  • utils/ — переиспользуемые функции: ожидания, генерация данных, маленькие хелперы.
  • Если проект небольшой, допустимо начать проще:

  • Хранить фикстуры прямо в conftest.py.
  • Иметь только tests/ и pages/.
  • Роли файлов конфигурации

    requirements.txt или pyproject.toml

    Это место, где фиксируются зависимости.

    Часто нужны:

  • pytest
  • selenium
  • Установка чаще всего делается через pip install -r requirements.txt.

    pytest.ini

    Файл настроек Pytest. Обычно туда кладут:

  • Маркеры (чтобы Pytest не ругался на неизвестные).
  • Параметры запуска по умолчанию.
  • Пример:

    Минимальный пример: фикстура браузера, страница и тест

    Ниже упрощённый пример, чтобы увидеть границы ответственности.

    Фикстура WebDriver

    Идея:

  • До yield мы создаём ресурс.
  • После yield гарантированно закрываем браузер.
  • BasePage и Page Object

    Обратите внимание на приём *self.USERNAME:

  • Локатор хранится как пара (By, selector).
  • find_element(*locator) разворачивает пару в два аргумента.
  • Тест

    Ключевое правило:

  • Тест описывает что проверяем.
  • Page Object описывает как именно взаимодействовать со страницей.
  • Договорённости по качеству кода в курсе

    Чтобы проект оставался поддерживаемым:

  • Одна ответственность на модуль: тесты отдельно, страницы отдельно.
  • Минимизируем “магические” ожидания и time.sleep, предпочитаем явные ожидания.
  • Не дублируем локаторы в разных тестах.
  • Делаем методы страниц “говорящими”: login, open, search, add_to_cart.
  • Типичные ошибки новичков и как их избежать

  • Хранить локаторы прямо в тестах: в итоге любые правки UI ломают всё.
  • Делать слишком “умные” page objects: страница должна описывать действия на странице, а не проверять бизнес-правила целиком.
  • Использовать sleep вместо ожиданий: тесты становятся медленными и нестабильными.
  • Мешать уровни: когда тест начинает искать элементы напрямую, минуя методы page object.
  • Что будет дальше

    В следующих материалах курса мы последовательно углубим этот каркас:

  • Научимся правильно ждать элементы и состояния страницы.
  • Организуем конфигурацию (браузер, окружение, таймауты) без хардкода.
  • Расширим POM: базовые методы, переиспользуемые компоненты, читаемые проверки.
  • Добавим практики, которые улучшают стабильность UI-тестов.
  • 2. Настройка окружения: Python, зависимости, драйверы, конфиги

    Настройка окружения: Python, зависимости, драйверы, конфиги

    В предыдущей статье мы договорились о стеке Pytest + Selenium + Page Object Model (POM) и базовой структуре проекта. Теперь соберём рабочее окружение так, чтобы тесты запускались одинаково на любом компьютере и в CI.

    Наша цель в этой статье:

  • поставить Python и настроить изоляцию зависимостей;
  • установить Pytest и Selenium;
  • корректно подготовить браузерные драйверы (без ручной боли);
  • завести минимальные конфиги проекта: pytest.ini, requirements.txt и настройки окружения.
  • !Схема шагов: от установки Python до запуска первых тестов

    Установка Python и проверка версии

    Для курса нужен установленный Python. Рекомендуемая версия: Python 3.11+ (можно 3.10+, если в компании стандартизировано).

  • Установите Python с официального сайта: Python Downloads.
  • Убедитесь, что Python доступен в терминале:
  • Если в вашей системе команда python не найдена, попробуйте:

    Важно:

  • Pytest и Selenium работают через интерпретатор Python, поэтому версия должна быть одинаковой для всей команды.
  • Лучше не ставить зависимости глобально в систему, чтобы проекты не конфликтовали между собой.
  • Изоляция зависимостей: виртуальное окружение

    Виртуальное окружение (virtual environment) — это отдельная папка, где хранятся зависимости конкретного проекта. Так версии библиотек не конфликтуют с другими проектами.

    В Python для этого есть модуль venv: Документация venv.

    Создание окружения в корне проекта:

    Активация:

    Проверка, что вы действительно внутри окружения:

    Правило проекта:

  • папку .venv обычно не коммитят в репозиторий; её создают локально.
  • Установка зависимостей: pip и requirements.txt

    pip — стандартный менеджер пакетов Python: Документация pip.

    Минимальные зависимости для старта:

  • pytest — запуск тестов;
  • selenium — управление браузером.
  • Создайте файл requirements.txt в корне проекта:

    Установите зависимости:

    Почему мы фиксируем версии через ==:

  • Чтобы у всей команды был одинаковый результат запуска.
  • Чтобы обновления библиотек не ломали тесты неожиданно.
  • Обновлять версии лучше осознанно: отдельным коммитом, с прогоном тестов.

    Установка браузера и работа с драйверами

    Selenium управляет браузером через WebDriver. Исторически нужно было вручную скачивать драйвер (ChromeDriver, GeckoDriver) под версию браузера. Сейчас в Selenium есть Selenium Manager, который умеет автоматически находить и скачивать нужный драйвер.

    Официальная документация: Selenium WebDriver: Install drivers.

    Что нужно сделать практически:

  • установите сам браузер (например, Google Chrome или Mozilla Firefox);
  • используйте свежую версию Selenium (в курсе мы её ставим через requirements.txt);
  • создавайте драйвер браузера обычным способом, без ручной загрузки драйверов.
  • Пример для Chrome:

    Пример для Firefox:

    Типичные причины проблем с драйверами:

  • корпоративные прокси или ограничения на скачивание бинарников;
  • очень старая версия браузера;
  • запуск в контейнере или CI без браузера.
  • Если Selenium Manager не подходит вашей инфраструктуре, запасной путь — использовать заранее установленный драйвер и прописывать путь к нему. Но это крайний вариант, потому что его сложнее поддерживать.

    Базовые конфиги проекта

    pytest.ini

    pytest.ini — это файл настроек Pytest, который влияет на запуск тестов во всём проекте.

    Документация: Pytest: Configuration.

    Минимальный пример pytest.ini:

    Что это даёт:

  • addopts = -q делает вывод короче;
  • markers заранее объявляет маркеры, чтобы Pytest не выдавал предупреждения;
  • testpaths фиксирует, где лежат тесты.
  • requirements.txt

    Мы уже создали requirements.txt. Дальше по курсу он будет расти, например за счёт библиотек для отчётов или управления окружениями.

    Подход на практике:

  • учебный проект: requirements.txt достаточно;
  • большой проект: можно перейти на pyproject.toml, но это необязательно для старта.
  • .gitignore

    Чтобы не коммитить лишнее, добавьте .gitignore.

    Минимальный набор для Python UI-автотестов:

    Настройки окружения: URL, таймауты, браузер

    Нам нужно избегать хардкода в тестах. Например, базовый URL тестового стенда или таймауты ожиданий должны настраиваться без переписывания кода.

    Простой подход для начала курса:

  • Хранить настройки в модуле config/settings.py.
  • Позже расширить: читать значения из переменных окружения (чтобы в CI можно было подменять стенд).
  • Пример config/settings.py:

    Пояснение:

  • BASE_URL — адрес приложения;
  • DEFAULT_TIMEOUT — базовый таймаут ожиданий Selenium (в секундах);
  • BROWSER — выбранный браузер по умолчанию.
  • Минимальный запуск проекта: проверка, что всё работает

    Создайте тест, который просто открывает страницу.

    tests/test_smoke_open.py:

    Запуск:

    Если тест открыл браузер и прошёл, значит:

  • Python установлен корректно;
  • виртуальное окружение работает;
  • зависимости установлены;
  • Selenium может поднять браузер и управлять им.
  • Частые ошибки и как их диагностировать

    Команда pytest не найдена

    Причины:

  • не активировано виртуальное окружение;
  • зависимости установлены не в то окружение.
  • Решение:

    Запуск через python -m pytest полезен тем, что гарантирует использование текущего интерпретатора.

    Браузер не стартует, ошибки про драйвер

    Причины:

  • браузер не установлен;
  • ограничен доступ на скачивание драйверов;
  • несовместимые версии Selenium и браузера.
  • Действия:

  • Проверьте, что браузер запускается вручную.
  • Обновите Selenium в requirements.txt и переустановите зависимости.
  • Ознакомьтесь с механизмом Selenium Manager: Selenium WebDriver: Install drivers.
  • Тесты нестабильны из-за скорости загрузки страниц

    На этом шаге многие добавляют time.sleep. Мы так делать не будем: дальше по курсу разберём явные ожидания Selenium и вынесем их в удобные методы базовой страницы.

    Что дальше

    Теперь у вас готово окружение и минимальный проект, который способен запускать UI-тесты.

    В следующих материалах мы:

  • перенесём создание WebDriver в общие фикстуры (conftest.py или fixtures/);
  • начнём использовать POM: выделим BasePage, локаторы и методы действий;
  • добавим явные ожидания вместо sleep, чтобы тесты стали стабильнее.
  • 3. Основы Selenium WebDriver: локаторы, действия, ожидания

    Основы Selenium WebDriver: локаторы, действия, ожидания

    В прошлых статьях мы:

  • выбрали стек Pytest + Selenium + Page Object Model (POM) и договорились о структуре проекта;
  • настроили окружение, зависимости и базовые конфиги.
  • Теперь нам нужно уверенно владеть базовыми приёмами Selenium WebDriver, без которых UI-тесты будут нестабильными:

  • как находить элементы на странице (локаторы);
  • как взаимодействовать с ними (действия);
  • как ждать правильного состояния интерфейса (ожидания вместо sleep).
  • !Схема показывает типичный цикл UI-теста: ожидание → действие → проверка

    Минимальная модель Selenium: driver → element

    Selenium управляет браузером через объект WebDriver (обычно переменная driver).

    Основные операции:

  • driver.get(url) открывает страницу.
  • driver.find_element(...) находит один элемент.
  • driver.find_elements(...) находит список элементов.
  • element.click(), element.send_keys(...) выполняют действия.
  • В POM важно помнить границу ответственности:

  • page object хранит локаторы и умеет выполнять действия на странице;
  • тест вызывает методы страниц и делает проверки.
  • Локаторы: как находить элементы

    Локатор — это способ сказать Selenium, какой именно элемент нужен.

    В Python для локаторов обычно используют класс By из Selenium.

    Документация Selenium по локаторам: Selenium WebDriver Locators.

    Кортеж локатора в POM

    Стандартная форма локатора в POM:

    Почему это удобно:

  • локатор лежит в одном месте;
  • find_element(*LOGIN_BUTTON) аккуратно разворачивает кортеж в аргументы.
  • Пример:

    Основные стратегии By

    На практике чаще всего используются:

  • By.ID
  • By.NAME
  • By.CSS_SELECTOR
  • By.XPATH
  • By.CLASS_NAME (использовать осторожно)
  • By.LINK_TEXT (часто хрупкий, зависит от текста)
  • CSS vs XPath

    Оба подхода рабочие, но важно понимать компромиссы.

    | Критерий | CSS-селекторы | XPath | |---|---|---| | Читаемость в простых случаях | чаще проще | иногда сложнее | | Поиск по атрибутам | удобно | удобно | | Поиск по тексту | не поддерживается напрямую | поддерживается | | Поиск относительно структуры | ограниченно | очень мощно |

    Примеры CSS:

    Примеры XPath:

    Какими должны быть локаторы, чтобы тесты жили долго

    Основные правила стабильных локаторов:

  • предпочтительны уникальные атрибуты: id, name, data-test, data-testid;
  • не привязывайтесь к хрупким деталям верстки, которые часто меняются;
  • избегайте слишком длинных цепочек по DOM.
  • Надёжные варианты обычно такие:

  • By.ID если id стабильный;
  • By.CSS_SELECTOR по data-test.
  • Пример хорошей практики:

    Плохие признаки локатора:

  • использование авто-сгенерированных классов (часто это классы из UI-библиотек);
  • XPath вида //div[2]/div[1]/div[3]/button.
  • find_element и find_elements

  • find_element возвращает один элемент или выбрасывает исключение.
  • find_elements возвращает список (возможно пустой), исключение обычно не выбрасывает.
  • Пример:

    Действия: как взаимодействовать с элементами

    Документация Selenium по взаимодействию с элементами: Selenium WebDriver Elements.

    Базовые действия

    Самые частые методы:

  • click()
  • send_keys(text)
  • clear()
  • чтение текста: element.text
  • чтение атрибута: element.get_attribute("value")
  • Пример ввода текста:

    Важно:

  • element.text — это видимый текст элемента.
  • для полей ввода текущее значение часто нужно читать через get_attribute("value").
  • Клавиши

    Для специальных клавиш используют Keys.

    Документация: Keys (Selenium).

    Пример:

    Выпадающие списки select

    Если на странице используется HTML-тег select, применяют Select.

    Документация: Select (Selenium).

    Пример:

    Если выпадающий список не select, а кастомный компонент, то взаимодействие будет через обычные клики по элементам списка, и почти всегда потребуются ожидания.

    Сложные действия: ActionChains

    Для drag-and-drop, hover, удержания кнопки мыши и других цепочек используют ActionChains.

    Документация: Action API (Selenium).

    Пример наведения мыши:

    Типичные ошибки при действиях

    Часто встречаются такие проблемы:

  • элемент найден, но по нему нельзя кликнуть: перекрыт оверлеем, ещё не кликабелен;
  • элемент исчез и появился заново после перерендера: ссылка на старый объект становится невалидной;
  • действие происходит слишком рано, до загрузки страницы.
  • Все эти проблемы решаются правильными ожиданиями и аккуратной работой с элементами.

    Ожидания: как сделать тесты стабильными

    UI — асинхронный: элементы появляются не мгновенно, а после запросов, анимаций и перерендера. Поэтому find_element и click без ожиданий часто дают флаки.

    Документация Selenium по ожиданиям: Waits (Selenium).

    Почему не time.sleep

    time.sleep(2):

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

  • ждут до наступления условия;
  • завершаются сразу, как только условие выполнено;
  • падают с понятным исключением по таймауту.
  • Неявные ожидания (implicit wait)

    Implicit wait задаётся один раз:

    Это значит: Selenium будет пытаться найти элемент до 5 секунд.

    Минусы implicit wait в реальных проектах:

  • он действует глобально и может маскировать проблемы;
  • он плохо комбинируется с явными ожиданиями в сложных сценариях.
  • В большинстве проектов предпочтение отдают явным ожиданиям.

    Явные ожидания (explicit wait) через WebDriverWait

    Явное ожидание создаётся там, где оно нужно:

    Ключевая идея:

  • until опрашивает условие, пока оно не станет истинным;
  • если условие не наступило за 10 секунд, будет timeout.
  • Часто используемые expected_conditions

    На практике чаще всего применяют:

  • presence_of_element_located — элемент есть в DOM, но может быть невидим.
  • visibility_of_element_located — элемент видим.
  • element_to_be_clickable — элемент видим и доступен для клика.
  • invisibility_of_element_located — элемент исчез или стал невидим.
  • url_contains или title_contains — проверка перехода.
  • staleness_of — ожидание, что старый элемент “устареет” после перерендера.
  • Важно выбирать условие по смыслу:

  • если нужно ввести текст, обычно подходит visibility_of_element_located;
  • если нужно нажать кнопку, чаще нужен element_to_be_clickable;
  • если нужно дождаться, что загрузчик исчез, нужен invisibility_of_element_located.
  • Правильное место ожиданий в POM

    Хороший стиль: ожидания живут рядом с действием, то есть внутри методов page object или в общих методах BasePage.

    Пример минимального BasePage с ожиданиями:

    Тогда page object становится компактным:

    А тест остаётся читаемым:

    Мини-чеклист устойчивого UI-теста

    Перед тем как писать новые тесты, держите в голове:

  • локаторы должны быть устойчивыми, по возможности через data-test;
  • действия должны выполняться только после явного ожидания нужного состояния;
  • ожидание должно соответствовать действию: видимость для ввода, кликабельность для клика;
  • не используйте sleep как основной инструмент синхронизации;
  • стремитесь прятать Selenium-детали (локаторы и ожидания) внутри POM.
  • Что дальше

    На следующем шаге курса мы начнём “собирать” удобный каркас проекта:

  • вынесем создание драйвера и параметры запуска в фикстуры Pytest;
  • добавим конфигурирование браузера и таймаутов без хардкода;
  • расширим BasePage, чтобы типовые ожидания и действия были единообразными по всему проекту.
  • 4. Pytest на практике: тесты, фикстуры, параметры, марки, хуки

    Pytest на практике: тесты, фикстуры, параметры, марки, хуки

    В прошлых статьях мы собрали окружение и разобрали основы Selenium: локаторы, действия и ожидания, а также закрепили идею Page Object Model (POM). Теперь добавим недостающий слой, который превращает набор Selenium-скриптов в управляемый тестовый проект: Pytest.

    В этой статье разберём, как в UI-автотестах на Pytest + Selenium + POM правильно:

  • писать и организовывать тесты
  • управлять жизненным циклом браузера через фикстуры
  • запускать один и тот же тест с разными данными через параметризацию
  • группировать и выбирать тесты через марки
  • подключать хуки Pytest для практичных задач вроде скриншота при падении
  • !Жизненный цикл фикстур и место conftest.py в проекте

    Как Pytest устроен в контексте UI-автотестов

    Для UI-автотестов Pytest чаще всего отвечает за:

  • обнаружение и запуск тестов
  • фикстуры для подготовки окружения (браузер, базовый URL, логирование)
  • параметризацию для разных данных и конфигураций
  • маркировки для выборочного запуска (smoke, regression)
  • хуки для реакций на события (например, падение теста)
  • Selenium при этом остаётся внутри фикстур и Page Object классов, а тесты описывают сценарии.

    Тесты: как Pytest находит и запускает проверки

    Соглашения Pytest по автообнаружению:

  • файлы: test_.py или _test.py
  • функции: test_*
  • классы: Test* (обычно без __init__)
  • Официальная документация: Pytest: Good Practices.

    Минимальный тест UI-уровня выглядит так:

    Практические правила для assert в UI:

  • проверяйте то, что действительно отражает состояние интерфейса
  • избегайте проверок “на глаз”, которые могут быть нестабильными (например, случайный порядок элементов)
  • для полей ввода читайте значение через get_attribute("value"), а не через text
  • Фикстуры: главный инструмент управления браузером

    Фикстура в Pytest — это функция, которая подготавливает данные или состояние для теста и возвращает его.

    В UI-тестах основная фикстура почти всегда driver.

    Документация: Pytest: Fixtures.

    Фикстура WebDriver с yield

    Классический шаблон:

    Как это работает:

  • код до yield выполняется до теста (подготовка)
  • значение после yield попадает в тест
  • код после yield выполняется после теста (очистка), даже если тест упал
  • Где хранить фикстуры: conftest.py

    conftest.py — специальный файл Pytest, который автоматически подхватывается в рамках директории.

    Типичный подход для курса:

  • общие фикстуры хранить в корневом conftest.py
  • по мере роста проекта выносить части в отдельные модули, но экспортировать через conftest.py
  • Встроенная “подстановка” фикстур в тест

    Pytest связывает фикстуры с тестом по имени аргумента:

    Важно:

  • не нужно вызывать фикстуру как функцию
  • Pytest сам создаст объект driver и передаст его в тест
  • Scope фикстур: как часто создавать браузер

    У фикстур есть параметр scope, который определяет, как часто фикстура создаётся заново.

    Документация: Pytest: Fixture scopes.

    | scope | Как часто создаётся | Когда применять в UI | |---|---|---| | function | на каждый тест | по умолчанию, самый безопасный вариант | | class | на класс тестов | если в классе много сценариев и нужен общий логин | | module | на файл | иногда для ускорения локального прогона | | session | на весь запуск | осторожно, риск взаимного влияния тестов |

    Пример scope="session" для base_url, потому что он не требует очистки браузера:

    autouse: фикстура без явного указания

    Если поставить autouse=True, фикстура будет применяться автоматически.

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

    Параметризация: один тест, много наборов данных

    Параметризация позволяет запустить один тест несколько раз с разными входными данными.

    Документация: Pytest: Parametrize.

    @pytest.mark.parametrize

    Пример для проверки валидации логина:

    Что важно в UI:

  • для каждого набора данных тест должен начинаться в одинаковом состоянии
  • фикстуры и подготовка должны гарантировать изоляцию
  • ids: читаемые имена прогонов

    Чтобы в отчёте было видно, какой именно набор данных упал:

    Марки: как группировать тесты и запускать только нужное

    Марки — это метки, которыми вы помечаете тесты. По маркам удобно собирать разные “срезы”:

  • smoke для быстрых критичных сценариев
  • regression для полного набора
  • ui если в проекте появятся также API-тесты
  • Документация: Pytest: Mark.

    Объявление маркеров в pytest.ini

    Чтобы Pytest не предупреждал о неизвестных маркерах:

    Использование маркеров в тестах

    Выборочный запуск

    Типичные команды:

    Практичные хуки Pytest для UI-тестов

    Хуки — это функции, которые Pytest вызывает в определённые моменты жизненного цикла запуска.

    Документация: Pytest: Reference (Hooks).

    В UI-автотестах хуки часто используют для:

  • скриншота при падении
  • логов браузера
  • добавления CLI-параметров вроде --base-url или --browser
  • Хук для скриншота при падении теста

    Базовая идея:

  • Pytest сообщает, что тест упал
  • мы берём driver из фикстур теста
  • сохраняем скриншот в папку artifacts/
  • Пример для conftest.py:

    Пояснения к ключевым частям:

  • item.funcargs содержит аргументы теста, включая фикстуры
  • report.when == "call" означает, что падение произошло в теле теста, а не в setup или teardown
  • save_screenshot сохраняет изображение текущего состояния страницы
  • Дальше по курсу такой артефакт удобно прикручивать к отчётам, но даже без отчётов это сильно ускоряет расследование падений.

    CLI-опции: --base-url и --browser

    Чтобы не хардкодить URL и браузер в коде, можно добавить опции запуска.

    Пример:

    Использование:

    На следующем шаге курса это удобно связать с фикстурой driver, чтобы переключать браузер без правок кода.

    Рекомендованный минимум для UI-проекта на Pytest

    Чтобы ваш проект оставался поддерживаемым, держитесь следующих правил:

  • фикстура driver создаёт и закрывает браузер, тесты не управляют жизненным циклом напрямую
  • ожидания и взаимодействия с UI живут в POM, а не в тестах
  • параметризация используется для данных, а не для копирования тестов
  • марки smoke и regression объявлены в pytest.ini и реально используются
  • хуки применяются точечно, когда дают практическую пользу, например скриншот при падении
  • Что дальше

    Теперь у нас есть полный “каркас” запуска UI-тестов:

  • Selenium даёт управление браузером
  • POM прячет локаторы, ожидания и действия в слой страниц
  • Pytest управляет жизненным циклом, данными, группировками и расширениями
  • В следующих материалах курса мы будем расширять инфраструктуру проекта:

  • аккуратно конфигурировать WebDriver под разные браузеры и режимы запуска
  • улучшать BasePage и переиспользуемые ожидания
  • готовить проект к стабильному запуску в CI
  • 5. Page Object Model: страницы, элементы, шаги, переиспользование

    Page Object Model: страницы, элементы, шаги, переиспользование

    В прошлых статьях мы настроили окружение, разобрали Selenium (локаторы, действия, ожидания) и Pytest (фикстуры, параметры, марки, хуки). Теперь соберём это в поддерживаемую архитектуру через Page Object Model (POM).

    POM помогает решить главную проблему UI-тестов: интерфейс меняется, а тесты должны оставаться читаемыми и ремонтироваться точечно.

    Основная идея POM хорошо сформулирована как паттерн Page Object: каждый экран приложения представлен объектом, который инкапсулирует детали UI-взаимодействий и предоставляет понятные методы действий. См. описание паттерна у Мартина Фаулера: PageObject (Martin Fowler).

    !Диаграмма показывает разделение ответственности: тесты вызывают шаги/страницы, страницы используют компоненты, всё работает через WebDriver и ожидания.

    Что именно мы строим в POM

    В учебном проекте удобно разделить UI-слой на 4 уровня:

  • Страница (page object): представляет экран или крупный раздел приложения (LoginPage, CartPage).
  • Элемент/компонент (component): переиспользуемая часть UI, которая встречается на разных страницах (Header, SideMenu, Toast).
  • Шаги (steps): слой “бизнес-действий” поверх страниц, если тестам нужны короткие, повторяемые последовательности (логин, создание сущности, заполнение формы).
  • База (BasePage): общие методы ожиданий и действий (клик, ввод, чтение текста, ожидание исчезновения).
  • Важное правило курса:

  • тесты описывают что проверяем;
  • страницы/компоненты описывают как взаимодействовать с UI;
  • шаги описывают типовые бизнес-последовательности, но не подменяют тестовые проверки.
  • Предлагаемая структура проекта

    Минимально расширим структуру из предыдущих статей, добавив компоненты и шаги:

    Когда что использовать:

  • pages/ — страницы и их API.
  • components/ — переиспользуемые части UI.
  • steps/ — повторяемые бизнес-последовательности, которые встречаются во многих тестах.
  • BasePage как фундамент: единые ожидания и действия

    В статье про Selenium мы уже делали базовые ожидания через WebDriverWait и expected_conditions. Зафиксируем это как стандарт проекта.

    Документация Selenium по ожиданиям: Waits (Selenium).

    Пример BasePage, который задаёт “язык” взаимодействий:

    Почему так:

  • ожидания живут рядом с действиями, поэтому тесты меньше страдают от флаков;
  • у всего проекта единый способ клика и ввода;
  • при изменении подхода (например, логирование каждого клика) правится один слой.
  • Страница: локаторы + методы действий (без тестовой логики)

    Класс страницы обычно содержит:

  • локаторы в формате (By, selector);
  • методы действий (ввод, клик, переход);
  • методы чтения состояния (получить текст, проверить видимость).
  • Что обычно не нужно делать в странице:

  • не писать внутри страницы “сценарии на 10 шагов” для всех случаев;
  • не делать “мегаметоды” типа create_user_and_verify_everything();
  • не смешивать проверки уровня теста с действиями (исключение: простые методы состояния вроде is_error_visible).
  • Пример LoginPage:

    Замечания:

  • методы возвращают self, чтобы можно было делать цепочки вызовов в тесте;
  • is_error_visible — метод состояния, который удобно проверять в тесте;
  • реальный проект чаще использует конкретные исключения Selenium, но на старте важно понять принцип.
  • Возврат “следующей страницы” как контракт

    Частый приём: если действие приводит к другому экрану, метод возвращает объект следующей страницы. Так тесты читаются как сценарий.

    Пример:

    Тогда тест становится проще:

    Контракт метода читается так: submit_valid возвращает DashboardPage.

    Компоненты: переиспользуемые “мини-страницы”

    Если один и тот же блок интерфейса есть на разных страницах, он должен жить в components/, иначе вы начнёте копировать локаторы и методы.

    Пример компонента Toast (всплывающее уведомление):

    Как использовать в странице:

    И в тесте:

    Ключевая идея: компонент получает доступ к базовым действиям через page, не создаёт свой driver и не ломает архитектуру.

    Шаги: когда они нужны и как не превратить их в “вторые тесты”

    Слой steps/ полезен, когда в десятках тестов повторяются одни и те же последовательности. Например:

  • вход в систему;
  • создание сущности через UI;
  • заполнение большой формы.
  • Правила слоя шагов:

  • шаги не должны содержать тяжёлых assert, которые “прячут” проверки от теста;
  • шаги могут делать мягкие проверки “что мы на правильной странице”, если это помогает стабильности, но ключевые ожидания результата всё равно остаются в тестах;
  • шаги должны возвращать страницу, на которой оказались, чтобы тест продолжал сценарий.
  • Пример auth_steps.py:

    Использование:

    Если шагов становится слишком много, это сигнал, что у вас:

  • либо тесты не изолированы и требуют сложных подготовок;
  • либо некоторые “шаги” на самом деле должны стать методами страниц/компонентов.
  • Локаторы в POM: договорённости, которые экономят время

    Из статьи про Selenium уже есть базовые правила. В контексте POM они становятся особенно важны:

  • локаторы должны жить в одном месте: в странице или компоненте;
  • предпочтительны data-test или data-testid атрибуты;
  • локатор должен быть устойчивым: без привязки к случайным классам и длинной структуре DOM.
  • Таблица “где какой локатор уместнее”:

    | Ситуация | Рекомендуемый подход | Почему | |---|---|---| | Есть стабильный id | By.ID | быстро, читабельно, обычно устойчиво | | Команда добавила data-test | By.CSS_SELECTOR по data-test | лучший вариант для тестов | | Нужно найти по тексту | By.XPATH с contains(., '...') | CSS не ищет по тексту напрямую | | Нужна сложная логика по дереву | XPath | мощнее в “структурных” запросах |

    Что именно переиспользовать, чтобы проект не раздувался

    В POM обычно переиспользуют не “всё подряд”, а конкретные типы вещей:

  • действия и ожидания в BasePage (клик, ввод, ожидание исчезновения);
  • компоненты (Header, Toast, Modal);
  • навигацию (переходы по меню лучше спрятать в компонент меню);
  • типовые данные (например, генерация email) в utils/, но не в страницах.
  • Что опасно переиспользовать:

  • гигантские универсальные методы “на все случаи”;
  • абстракции, которые скрывают важные детали теста (например, шаг, который сам решает, что и как проверять).
  • Мини-чеклист качества Page Object

    Перед тем как коммитить новый page object, проверьте:

  • локаторы лежат в одном месте и не дублируются в тестах;
  • методы страницы выполняют действия через ожидания (а не через sleep);
  • имя методов читается как действие пользователя (login_as, save, open_settings);
  • страница не содержит бизнес-проверок, которые должны быть в тестах;
  • повторяющийся блок UI вынесен в компонент.
  • Как это связывается с Pytest и фикстурами

    POM не заменяет Pytest, а опирается на него:

  • фикстура driver создаёт и закрывает браузер;
  • фикстура base_url задаёт окружение;
  • тесты создают страницы, передавая driver в конструктор;
  • хуки Pytest (например, скриншот при падении) работают независимо от POM и помогают расследованию.
  • Если вы ещё не вынесли driver и base_url в фикстуры, вернитесь к статье про Pytest и зафиксируйте каркас через conftest.py.

    Что дальше

    На следующем этапе курса обычно усиливают инфраструктуру вокруг POM:

  • конфигурация WebDriver под разные режимы (headless, размеры окна, разные браузеры);
  • единые политики ожиданий и ретраев в BasePage;
  • подготовка к CI: артефакты, отчёты, стабильные прогоны.
  • 6. Стабильность и поддерживаемость: ожидания, PageFactory/обёртки, тестовые данные

    Стабильность и поддерживаемость: ожидания, PageFactory/обёртки, тестовые данные

    В предыдущих материалах мы собрали базовый стек Pytest + Selenium + POM, научились пользоваться локаторами, действиями и явными ожиданиями, а также выстроили слой страниц, компонентов и шагов.

    Теперь доведём проект до состояния, когда UI-тесты:

  • стабильно проходят на разных машинах и в CI;
  • предсказуемо синхронизируются с интерфейсом;
  • легко ремонтируются при изменениях UI;
  • не разваливаются из-за плохо организованных тестовых данных.
  • !Где живут ожидания, обёртки и тестовые данные в архитектуре проекта

    Почему UI-тесты становятся нестабильными

    Флаки в UI чаще всего возникают не из-за Selenium как такового, а из-за несовпадения ожиданий теста и реальной асинхронной природы интерфейса.

    Типичные причины нестабильности:

  • элемент ещё не появился в DOM, а тест уже пытается кликнуть;
  • элемент есть в DOM, но не видим из-за анимации или вкладки;
  • элемент видим, но клик перехватывает оверлей или лоадер;
  • страница перерендрилась, и сохранённый WebElement стал устаревшим;
  • тестовые данные конфликтуют с уже существующими данными на стенде.
  • Цель этой статьи: сделать так, чтобы большинство таких проблем решались архитектурно, а не точечными костылями.

    Политика ожиданий: что и где мы ждём

    В курсе мы используем явные ожидания через WebDriverWait и expected_conditions.

    Полезные источники:

  • Ожидания в Selenium
  • expected_conditions (Python API)
  • Практические договорённости проекта:

  • не используем time.sleep как основной способ синхронизации;
  • не включаем глобальный implicitly_wait в реальном проекте без чёткой причины;
  • не ждём "просто элемент" если нам нужно кликнуть, мы ждём кликабельность;
  • ожидания живут в BasePage и обёртках, а не размазываются по тестам.
  • Усиливаем BasePage: единые ожидания, единые ошибки

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

    Почему это повышает стабильность:

  • ожидание соответствует действию;
  • тесты меньше знают о Selenium-деталях;
  • меняется стратегия ожиданий в одном месте.
  • Ретраи для действий: когда они действительно нужны

    Даже при правильных ожиданиях иногда встречаются ошибки, которые логично лечить коротким ретраем:

  • StaleElementReferenceException после перерендера;
  • ElementClickInterceptedException когда оверлей исчезает чуть позже.
  • Ретраи нужно делать аккуратно:

  • ограниченное число попыток;
  • повторный поиск элемента на каждой попытке;
  • отсутствие длинных sleep, лучше опираться на ожидания.
  • Пример безопасного клика:

    Это не замена ожиданиям, а страховка от коротких гонок, которые не всегда удаётся устранить только условием.

    Главный анти-паттерн стабильности: кэшировать WebElement

    Очень частая ошибка в POM: сохранить WebElement как поле и дальше работать с ним.

    Почему это ломает тесты:

  • многие современные UI-фреймворки перерисовывают DOM, и старый элемент становится невалидным;
  • элемент может быть пересоздан после сортировки, фильтрации, обновления данных;
  • вы начинаете бороться с stale element в тестах, хотя проблема архитектурная.
  • Правило:

  • храним локаторы и обёртки;
  • WebElement получаем непосредственно перед действием.
  • PageFactory и обёртки: что делать в Python-проектах

    Термин PageFactory чаще встречается в Java-экосистеме Selenium: это подход, при котором элементы страницы описываются декларативно, а доступ к ним выдаётся через ленивые прокси.

    В Python обычно делают аналог через обёртки и дескрипторы, чтобы:

  • не дублировать wait_visible и wait_clickable по проекту;
  • улучшить читаемость страниц;
  • добавить логирование действий в одном месте;
  • не кэшировать WebElement.
  • Мини-обёртка элемента без кэширования

    Сделаем две сущности:

  • ElementHandle выполняет действия через BasePage и его ожидания;
  • UiElement является дескриптором и создаёт ElementHandle при обращении.
  • Использование на странице:

    Плюсы такого подхода:

  • вы получаете читаемость "как на языке действий";
  • вы не храните WebElement, а значит меньше stale element;
  • если нужно поменять стратегию клика или добавить логирование, это делается в одном месте.
  • Минусы и ограничения:

  • появляется дополнительный слой кода, который надо поддерживать;
  • важно не превращать обёртку в "второй Selenium" с сотней методов.
  • Тестовые данные: как сделать их предсказуемыми

    Даже идеально написанные ожидания не спасут, если тесты каждый раз получают разные данные или конфликтуют с данными на стенде.

    Принципы поддерживаемых тестовых данных:

  • тест должен ясно показывать, какие данные ему нужны;
  • данные должны быть воспроизводимыми в CI;
  • уникальные значения должны быть уникальными предсказуемо;
  • тест не должен зависеть от результатов предыдущих тестов.
  • Простые данные через фикстуры Pytest

    Для большинства UI-сценариев удобно описать данные маленькими структурами.

    Использование:

    Плюс: тест сразу показывает зависимости и не содержит магических строк.

    Параметризация как контроль сценариев, а не копирование тестов

    Генерация данных: осторожно с рандомом

    Генераторы данных полезны, если:

  • форма требует уникальных значений;
  • вы хотите покрыть больше вариаций строк.
  • Но неконтролируемый рандом делает падения невоспроизводимыми.

    Если вы используете генерацию данных, фиксируйте повторяемость:

  • задавайте seed;
  • логируйте сгенерированное значение при падении;
  • не генерируйте там, где достаточно констант.
  • Популярная библиотека для генерации данных:

  • Faker (PyPI)
  • Минимальный шаблон с seed:

    Если вы не хотите добавлять зависимости, можно генерировать уникальность через время или uuid, но это хуже для воспроизводимости.

    Данные и секреты: не храните пароли в коде без причины

    В учебном проекте допустимы тестовые креды в фикстуре, но в реальной инфраструктуре обычно:

  • читают логин и пароль из переменных окружения;
  • используют секреты CI;
  • разделяют пользователей под разные наборы тестов.
  • Практический итог: чеклист стабильного проекта

    Перед тем как расширять тестовый набор, проверьте:

  • в проекте нет системного time.sleep как главного механизма синхронизации;
  • клики и ввод идут через BasePage и его ожидания;
  • ретраи применяются точечно и ограниченно, а не маскируют ошибки;
  • страницы не кэшируют WebElement и не раздают их наружу как контракт;
  • локаторы лежат в POM-слое и по возможности основаны на data-test;
  • тестовые данные приходят через фикстуры и параметризацию, а генерация контролируема.
  • Дальше этот фундамент легко расширять: добавлять отчёты, логи, запуск в headless, параллельность и стабильный CI, не переписывая сами тесты и страницы.

    7. Запуски и отчёты: параллельность, Allure, CI и подготовка к продакшену

    Запуски и отчёты: параллельность, Allure, CI и подготовка к продакшену

    Мы уже собрали стек Pytest + Selenium + POM, вынесли ожидания в BasePage, настроили фикстуры и добавили полезные хуки (например, скриншот при падении). Следующий шаг — превратить проект из «локально запускается» в «стабильно запускается командой и в CI, с понятными отчётами и артефактами».

    В этой статье разберём:

  • как организовать удобные запуски Pytest
  • как ускорять прогон через параллельность и не сломать UI-тесты
  • как подключить Allure-отчёты и вкладывать артефакты (скриншоты, page source)
  • как собрать минимальный CI (на примере GitHub Actions)
  • что обычно подразумевается под «подготовкой к продакшену» в UI-автотестах
  • !Диаграмма жизненного цикла запуска тестов и генерации отчётов

    Запуски Pytest как интерфейс проекта

    Хороший проект автотестов — это не только код, но и предсказуемый способ запуска.

    Базовые команды запуска

  • Запуск всех тестов: pytest
  • Запуск конкретной папки: pytest tests/
  • Запуск конкретного теста: pytest tests/test_login.py::test_login_success
  • Более подробный вывод: pytest -vv
  • Остановка на первом падении: pytest -x
  • Документация по CLI Pytest: Pytest: Usage and Invocations.

    Марки и выборочные прогоны

    Если вы уже объявили марки в pytest.ini (например, smoke, regression, ui), они становятся основным механизмом сборки «срезов».

  • Запуск smoke: pytest -m smoke
  • Запуск только UI: pytest -m ui
  • Исключить долгие: pytest -m "not regression"
  • Документация: Pytest: Working with custom markers.

    Полезные настройки в pytest.ini

    Пример настроек, которые часто используют в UI-проектах:

    Пояснения:

  • -ra показывает сводку по пропущенным, xfail и другим статусам, что удобно в CI.
  • testpaths фиксирует корневую директорию тестов.
  • Параллельность: ускоряем прогон через pytest-xdist

    Параллельный запуск в Pytest обычно делают плагином pytest-xdist.

  • Плагин: pytest-xdist (PyPI)
  • Установка:

    Запуск в несколько воркеров:

    Автовыбор по числу CPU:

    Главная особенность UI-параллельности

    Параллельность в UI-тестах безопасна только если тесты изолированы. На практике это означает:

  • каждый тест создаёт свой браузер (обычно driver с scope="function")
  • тестовые данные не конфликтуют между воркерами
  • тесты не зависят друг от друга
  • Типичные источники конфликтов:

  • один и тот же тестовый пользователь используется многими тестами одновременно
  • создание одинаковых сущностей с одинаковыми именами
  • работа с общим состоянием стенда без очистки
  • Практические правила для xdist в UI-проекте

  • Не используйте driver с scope="session" для параллельного прогона.
  • Делайте уникальные данные предсказуемо: через отдельные аккаунты на воркеры, префиксы по воркеру или выделенный стенд.
  • Если вы используете фикстуры данных, следите, чтобы они не возвращали «общий» ресурс, который тесты модифицируют.
  • > Если параллельность даёт нестабильность, это обычно сигнал проблем с изоляцией, а не причина отключать параллельность навсегда.

    Отчёты: от минимальных артефактов до Allure

    Отчётность нужна, чтобы быстро ответить на вопросы:

  • какой тест упал
  • на каком шаге
  • что было на экране
  • какие входные данные и окружение
  • Минимальный уровень: встроенные возможности Pytest

    Самое простое, что полезно подключить почти всегда:

  • -ra для сводки
  • сохранение артефактов (скриншот, page source) в папку artifacts/
  • Также часто используют JUnit XML, чтобы CI-системы умели отображать результаты.

    Запуск с генерацией XML:

    Документация: Pytest: JUnitXML format.

    Allure: отчёты с шагами и вложениями

    Allure — популярный формат отчётов, который хорошо подходит UI-тестам: в него удобно вкладывать скриншоты и другие артефакты.

  • Документация: Allure Framework documentation
  • Плагин для Pytest: allure-pytest (PyPI)
  • Установка Python-плагина:

    Запуск тестов с сохранением результатов:

    Дальше results нужно превратить в HTML-отчёт. Для этого требуется Allure CLI.

  • Установка и использование CLI описаны в документации: Allure: Get started
  • Генерация отчёта:

    Просмотр локально:

    Вложения в Allure: скриншот и HTML страницы при падении

    Ранее мы делали скриншот через хук pytest_runtest_makereport. Для Allure логичнее сохранять не только файл на диск, но и прикладывать его в отчёт.

    Пример в conftest.py:

    Что это даёт:

  • артефакты остаются на диске (удобно как запасной вариант)
  • при наличии Allure они попадают в отчёт как вложения
  • Шаги Allure: где их держать в архитектуре POM

    Allure шаги можно добавлять:

  • в методы страниц и компонентов
  • в слой steps/
  • На практике чаще всего шаги добавляют в steps/, чтобы страницы не обрастали отчётностью.

    Пример:

    Правило поддерживаемости:

  • шаги должны помогать читать отчёт, но не должны превращать код в «шум из декораторов».
  • CI: минимальный стабильный запуск в GitHub Actions

    Цель CI для UI-тестов:

  • запускаться одинаково при каждом коммите
  • сохранять артефакты падений
  • давать отчёт, который можно открыть без локальной машины
  • Ниже пример для GitHub Actions.

  • Документация: GitHub Actions documentation
  • Пример workflow

    Создайте файл .github/workflows/ui-tests.yml:

    Что важно:

  • if: always() гарантирует загрузку артефактов даже если тесты упали.
  • --alluredir сохраняет результаты Allure, которые потом можно обработать.
  • Headless режим в Selenium

    В CI чаще всего запускают браузер в headless режиме. Это делается в фикстуре driver, через опции браузера.

    Пример для Chrome:

    Пояснения:

  • --window-size уменьшает риск «не влезло на экран» и неожиданных адаптивных состояний.
  • конкретный флаг headless может отличаться по версиям Chrome, поэтому в реальном проекте его фиксируют и проверяют при обновлениях.
  • Документация по Selenium WebDriver (включая настройки драйвера и capabilities): Selenium WebDriver documentation.

    Подготовка к продакшену: что должно быть в «взрослом» UI-проекте

    Под «продакшеном» в автотестах обычно имеют в виду не публикацию библиотеки, а готовность проекта:

  • жить долго
  • быть предсказуемым для команды
  • давать быстрый разбор падений
  • масштабироваться по числу тестов и окружений
  • Чеклист продакшен-готовности

  • Запуски стандартизированы: одна команда для smoke, одна для regression.
  • Конфигурация не захардкожена: base_url, браузер, headless читаются из CLI-опций или переменных окружения.
  • Скриншот и page source при падении сохраняются всегда.
  • Есть отчётный формат: минимум JUnit XML, лучше Allure.
  • Параллельность включается без хаоса в данных.
  • Нет системного time.sleep: синхронизация через ожидания в BasePage.
  • Локаторы стабильные: преимущественно data-test.
  • Тесты изолированы: любой тест можно запустить отдельно.
  • Типовые анти-паттерны, которые всплывают при масштабировании

  • Общий пользователь на все тесты.
  • Общий браузер на весь прогон (для ускорения), из-за которого тесты начинают влиять друг на друга.
  • Отсутствие артефактов падений, из-за чего разбор превращается в гадание.
  • Селекторы в тестах, а не в POM.
  • Итог

    Теперь ваш проект можно довести до практичного уровня эксплуатации:

  • вы умеете ускорять прогон через pytest-xdist
  • вы умеете собирать отчёты и вкладывать артефакты в Allure
  • вы можете настроить минимальный CI, который запускает тесты и сохраняет результаты
  • Дальше обычно идут «инженерные улучшения» вокруг уже построенного фундамента:

  • матрица окружений и браузеров в CI
  • публикация Allure Report как статического сайта
  • контейнеризация для воспроизводимости
  • интеграция с тест-менеджментом и уведомлениями