Автотесты UI на Python: Pytest + Selenium + Page Object Model

Курс научит писать поддерживаемые UI-автотесты на Python с использованием Pytest, Selenium WebDriver и паттерна Page Object Model (POM). Вы настроите проект, реализуете стабильные ожидания, запустите тесты в CI и получите практики для масштабирования тестового фреймворка.

1. Архитектура UI-автотестов: Pytest, Selenium и POM

Архитектура UI-автотестов: Pytest, Selenium и POM

Зачем вообще нужна архитектура в UI-автотестах

UI-тесты часто начинают писать “в лоб”: открыть страницу, найти элемент, кликнуть, проверить текст. На небольшом проекте это работает, но при росте количества тестов быстро появляются проблемы:

  • Дублирование кода (одни и те же локаторы и шаги в десятках тестов)
  • Сложность поддержки (поменялся селектор — упали сотни тестов)
  • Хрупкость (нестабильность из-за ожиданий, таймингов, анимаций)
  • Нечитаемость (тесты превращаются в “простыню” технических действий)
  • Цель хорошей архитектуры — сделать тесты:

  • читаемыми (чтобы сценарий был понятен как спецификация)
  • поддерживаемыми (изменения в UI локализуются в одном месте)
  • переиспользуемыми (общие действия вынесены в общий слой)
  • стабильными (ожидания и взаимодействия стандартизированы)
  • В этом курсе базовая связка будет такой:

  • Pytest как тестовый фреймворк (запуск, фикстуры, отчётность через плагины)
  • Selenium WebDriver как инструмент управления браузером
  • Page Object Model (POM) как архитектурный подход к организации кода
  • Роли Pytest и Selenium в UI-тестировании

    Что делает Pytest

    Pytest отвечает за “тестовую инфраструктуру”:

  • поиск и запуск тестов
  • фикстуры для подготовки окружения (драйвер, авторизация, тестовые данные)
  • параметризацию (один тест — много наборов данных)
  • хуки и плагины (например, отчёты)
  • Ключевая идея: тест должен описывать проверку, а подготовка и сервисные детали должны быть вынесены в фикстуры.

    Что делает Selenium

    Selenium отвечает за “управление браузером”:

  • открыть страницу
  • найти элемент
  • выполнить действие (клик, ввод текста, скролл)
  • получить состояние (текст, атрибуты, видимость)
  • Важно: Selenium — низкоуровневый инструмент. Если писать тесты напрямую на Selenium-командах, тестовый код быстро станет сложным и повторяющимся. Поэтому Selenium обычно “прячут” за POM-слоем.

    Что такое Page Object Model (POM)

    Page Object Model — это подход, где каждая страница (или значимый компонент) описывается отдельным классом, который:

  • хранит локаторы элементов
  • предоставляет методы “действий пользователя” (например, login, search, add_to_cart)
  • возвращает другие page-объекты как результат навигации
  • Хороший Page Object не должен “знать” про проверки теста. Он описывает как взаимодействовать со страницей, а тест описывает что проверить.

    Полезное чтение по паттерну: Martin Fowler — PageObject

    Слои в типичном UI-автопроекте

    Удобно мыслить проект как несколько уровней ответственности.

    !Диаграмма показывает разделение ответственности между тестами, POM-слоем, обёртками над Selenium и конфигурацией

    Уровень тестов

    Тесты в идеале выглядят как сценарии:

  • подготовка (через фикстуры)
  • шаги (через методы page-объектов)
  • проверки (assert)
  • Тесты не должны содержать:

  • “сырые” XPath/CSS локаторы
  • сложные ожидания WebDriverWait прямо в тесте
  • повторяющиеся шаги авторизации или навигации
  • Уровень Page Objects

    Page Object:

  • инкапсулирует локаторы
  • описывает действия пользователя методами
  • может содержать небольшие “проверки состояния страницы” (например, is_opened()), но не бизнес-ассерты теста
  • Базовый слой взаимодействия (BasePage / wrappers)

    Это слой, где живут:

  • стандартные ожидания (видимость, кликабельность)
  • безопасные клики/ввод
  • логирование действий
  • скриншоты при ошибках (часто через хуки Pytest)
  • Идея: все нестабильные и повторяющиеся части Selenium сосредоточены в одном месте.

    Конфигурация и тестовые данные

    Отдельно стоит хранить:

  • URL окружений (dev/stage/prod)
  • таймауты ожиданий
  • учётные данные (в идеале через переменные окружения/секреты)
  • тестовые наборы данных
  • Рекомендуемая структура проекта

    Один из рабочих вариантов структуры (её можно адаптировать под командные стандарты):

    Ключевые принципы:

  • tests/ содержит только тесты и минимум “техники”
  • pages/ содержит page-объекты
  • components/ содержит переиспользуемые куски UI (хедер, меню, модальные окна)
  • locators/ хранит локаторы отдельно, если в проекте их много (часто это удобно)
  • conftest.py содержит фикстуры Pytest
  • Минимальный каркас: Pytest + WebDriver фикстура

    Ниже пример базовой фикстуры, которая создаёт и закрывает драйвер. В реальном проекте вы добавите настройку браузера, headless-режим, размер окна и т.д.

    Почему полезно делать именно так:

  • yield гарантирует корректное освобождение ресурсов
  • тесты получают готовый driver как аргумент
  • управление жизненным циклом браузера централизовано
  • BasePage: единая точка для ожиданий и действий

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

    Здесь важны две идеи:

  • методы click/type/text_of — это контракт взаимодействия, одинаковый для всех страниц
  • ожидания живут в одном месте и их легко улучшать (например, добавить повторный клик при перехвате, скролл к элементу)
  • Документация по ожиданиям: Selenium — Waits

    Page Object: пример страницы логина

    Page Object обычно содержит:

  • локаторы
  • методы действий
  • методы проверки, что страница открыта (по уникальному элементу)
  • Обратите внимание:

  • тесту не нужно знать, какие именно селекторы у полей
  • если вёрстка поменялась, правка обычно локализуется в одном файле
  • Тест как сценарий: минимум деталей Selenium

    Признаки “здорового” теста:

  • читается как сценарий
  • содержит осмысленный assert
  • не содержит локаторов и WebDriverWait
  • Где заканчивается POM и начинаются фикстуры

    Частая ошибка — пытаться сделать “всё в POM” или “всё в фикстурах”. Практичное разделение:

  • фикстуры отвечают за подготовку (создать драйвер, залогиниться через UI или API, открыть стартовую страницу)
  • page-объекты отвечают за действия на конкретной странице/компоненте
  • тест отвечает за проверку результата
  • Если авторизация нужна в каждом тесте, это хороший кандидат на фикстуру:

    Локаторы: как договориться с командой

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

  • предпочитать устойчивые селекторы: data-testid, data-qa, id
  • использовать CSS там, где это возможно (часто он проще и быстрее читается)
  • XPath оставлять для случаев, где без него трудно (сложные деревья, поиск по тексту)
  • Если в продукте можно влиять на разметку, самый практичный подход — внедрять data-* атрибуты специально под автотесты.

    Компоненты вместо страниц (когда POM расширяется)

    Когда в приложении много повторяющихся элементов (хедер, меню, таблицы, модальные окна), полезно выделять компоненты.

    Идея:

  • Header содержит локаторы и методы для шапки
  • любая страница, где есть шапка, использует компонент
  • Так уменьшается дублирование и упрощается поддержка.

    Типовые ошибки архитектуры UI-автотестов

  • “Умные” Page Objects, которые делают assertions за тест: из-за этого тесты становятся менее гибкими и хуже диагностируются
  • Локаторы и ожидания в тестах: любое изменение UI ломает множество файлов
  • Отсутствие единого BasePage: разные ожидания и разные подходы к кликам в разных местах
  • Смешивание уровней: тест одновременно “рулит браузером” и проверяет бизнес-логику
  • Что будет дальше в курсе

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

  • аккуратная настройка Pytest (марки, параметризация, конфиги)
  • управление браузерами и режимами запуска (headless, размеры окна)
  • надёжные ожидания и борьба с флаки-тестами
  • расширенный POM: компоненты, навигация, базовые проверки открытости страниц
  • практики поддержки: локаторы, тестовые данные, отчёты
  • 2. Настройка окружения и структура проекта

    Настройка окружения и структура проекта

    Как эта тема связана с архитектурой Pytest + Selenium + POM

    В предыдущей статье мы договорились о разделении ответственности:

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

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

    Что понадобится

    Минимальный стек:

  • Python 3.10+ (рекомендуется 3.11+)
  • Pytest
  • Selenium 4
  • браузер (например, Google Chrome)
  • Почему важно фиксировать версии:

  • UI-тесты чувствительны к изменениям Selenium/WebDriver
  • одинаковые версии на ноутбуках и CI снижают количество “странных” падений
  • Официальные источники:

  • Документация Pytest
  • Документация Selenium WebDriver
  • Документация venv
  • Создание и активация виртуального окружения

    Виртуальное окружение нужно, чтобы зависимости проекта не смешивались с системным Python и другими проектами.

    Команды ниже выполняйте в корне проекта.

    Windows (PowerShell)

    macOS или Linux (bash/zsh)

    Проверка, что вы внутри окружения:

    Путь должен указывать на .venv.

    Установка зависимостей и фиксация версий

    Самый простой старт для курса: requirements.txt.

    Минимальный requirements.txt

    Установка:

    Пояснения:

  • pytest нужен для тестового раннера, фикстур и удобного запуска.
  • selenium нужен для управления браузером через WebDriver.
  • Если вы используете дополнительные плагины (например, отчёты), добавляйте их позже осознанно, чтобы не усложнять отладку на старте.

    Дополнительно про установку пакетов:

  • Руководство pip
  • WebDriver и браузеры: как это работает в современных версиях Selenium

    Раньше нужно было отдельно скачивать chromedriver и следить за соответствием версии драйвера версии браузера.

    В Selenium 4 есть Selenium Manager, который часто умеет автоматически находить или скачивать подходящий драйвер при создании webdriver.Chrome().

    Рекомендация для курса:

  • начать с webdriver.Chrome() без ручной установки драйверов
  • если в вашей среде автоустановка не сработает (часто в закрытых корпоративных сетях), тогда уже переходить к ручной настройке драйвера
  • Официально про драйверы и менеджмент браузеров:

  • Selenium WebDriver
  • Рекомендуемая структура проекта

    Ниже структура, которая хорошо поддерживает слои из прошлой статьи: тесты отдельно, POM отдельно, фикстуры отдельно.

    !Диаграмма показывает рекомендуемую структуру папок и зависимости между слоями

    Дерево каталогов

    Зачем так разделять

  • tests/ содержит только сценарии и проверки (assert).
  • src/pages/ содержит Page Objects: локаторы и действия пользователя.
  • src/components/ содержит повторяемые компоненты интерфейса (хедер, меню, модалки).
  • src/locators/ помогает вынести локаторы из страниц, когда их становится много.
  • src/config/ хранит настройки: URL, таймауты, режимы запуска.
  • conftest.py в корне даёт общие фикстуры для Pytest.
  • Важно: вы можете начать с локаторов прямо в page-классах (как в прошлой статье), а locators/ добавить позже, когда появится необходимость.

    Настройка Pytest: базовый pytest.ini

    Файл pytest.ini помогает централизовать запуск.

    Пример:

    Что это даёт:

  • Pytest будет искать тесты в tests/.
  • Имена файлов с тестами стандартизированы (test_*.py).
  • Можно добавлять общие опции запуска, не печатая их каждый раз.
  • Подробности про конфигурацию:

  • Pytest Configuration
  • Базовая фикстура драйвера в conftest.py

    Фикстура прячет создание и закрытие браузера, чтобы тесты получали готовый driver.

    Ключевые моменты:

  • yield гарантирует, что quit() выполнится даже если тест упал.
  • implicitly_wait(0) оставляет только явные ожидания в BasePage (это снижает нестабильность).
  • Минимальный каркас POM-слоя (чтобы структура сразу работала)

    BasePage как единая точка ожиданий

    Идея: тесты и страницы не должны каждый раз заново “изобретать” ожидания.

    Пример Page Object

    Как запускать тесты

    Из корня проекта:

    Запуск конкретного файла:

    Запуск конкретного теста по имени:

    Официально про запуск и фильтрацию:

  • Pytest Usage and Invocations
  • Практические соглашения, которые сэкономят время

    Имена тестов и файлов

  • Файлы: test_*.py
  • Функции: test_*
  • Имена должны описывать поведение: test_login_with_invalid_password лучше, чем test_login_2
  • Где хранить URL и таймауты

    Начните с простого src/config/settings.py:

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

    .gitignore

    Чтобы не коммитить мусор:

    Итог

    Теперь у вас есть:

  • изолированное окружение Python
  • зафиксированные зависимости
  • понятная структура проекта под Pytest + Selenium + POM
  • базовая фикстура драйвера и точка расширения в виде BasePage
  • Это фундамент, на который дальше можно накладывать маркеры, параметризацию, разные браузеры, headless-режим, отчёты и приёмы борьбы с флаки-тестами.

    3. Pytest: фикстуры, параметризация, маркировки и репорты

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

    Как Pytest “склеивает” Selenium и POM в стабильный проект

    В предыдущих статьях курса мы:

  • выбрали архитектуру Pytest + Selenium + Page Object Model
  • зафиксировали структуру проекта и базовую фикстуру driver
  • Теперь задача Pytest-части — сделать тесты:

  • короткими (всё подготовительное — в фикстурах)
  • масштабируемыми (параметризация вместо копипаста)
  • управляемыми (маркировки для отбора наборов тестов)
  • диагностируемыми (репорты и артефакты: логи, скриншоты)
  • > Главный принцип: тест описывает сценарий и проверки, а фикстуры и хуки — инфраструктуру.

    !Как Pytest (фикстуры/хуки) связывает тесты, POM и репорты

    Фикстуры: что это и зачем они нужны

    Фикстура в Pytest — это функция, которая:

  • готовит состояние для теста
  • отдаёт это состояние тесту (или другой фикстуре)
  • при необходимости выполняет очистку после теста
  • Документация: Pytest fixtures

    В UI-автотестах фикстуры чаще всего отвечают за:

  • создание и закрытие WebDriver
  • конфигурацию браузера (headless, размер окна)
  • авторизацию (через UI или API)
  • тестовые данные и подготовку окружения
  • Базовая фикстура драйвера (повторение и улучшение)

    В прошлой статье мы делали минимальную фикстуру. Добавим два улучшения:

  • возможность выбрать headless через опцию запуска
  • единый размер окна для воспроизводимости
  • Запуск:

  • обычный: pytest
  • headless: pytest --headless
  • Почему yield лучше, чем return

    yield превращает фикстуру в конструкцию setup/teardown:

  • до yield — подготовка
  • значение после yield — то, что получает тест
  • после yield — гарантированная очистка (даже если тест упал)
  • Это критично для Selenium, чтобы браузеры не оставались “висеть”.

    Где хранить фикстуры

    Практика для курса (и для большинства проектов):

  • общие фикстуры — в корневом conftest.py
  • специфичные фикстуры для папки tests/ — можно добавлять в tests/conftest.py
  • Pytest автоматически находит conftest.py, импортировать его вручную не нужно.

    Область видимости фикстур (scope)

    Scope определяет, как часто Pytest создаёт фикстуру заново.

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

    Важно: для UI-тестов function чаще всего наиболее стабильный, потому что состояние браузера не “протекает” между тестами.

    Пример:

    Композиция фикстур: фикстуры могут зависеть друг от друга

    Один из самых сильных приёмов Pytest: фикстуры можно “собирать” слоями.

    Пример “драйвер + открытая страница логина”:

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

    Автоприменяемые фикстуры (autouse)

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

    Это полезно для “политик проекта”, например:

  • выставлять единый язык/локаль
  • чистить cookies
  • включать логирование
  • Но autouse делает зависимости менее явными, поэтому используйте его умеренно.

    Документация: Fixture scopes and autouse

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

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

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

    Простой пример @pytest.mark.parametrize

    Что вы получаете:

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

    По умолчанию Pytest показывает параметры, но иногда удобнее задать короткие имена:

    Когда параметризацию лучше “вынести” в фикстуру

    Если наборы данных нужны в нескольких тестах, их удобно отдавать фикстурой:

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

  • если данные нужны только одному тесту — parametrize
  • если данные переиспользуются — фикстура с params
  • Маркировки: управление наборами тестов

    Марки (markers) — это метки, которыми вы помечаете тесты, чтобы потом выбирать, пропускать или ожидать падения.

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

    Практические марки для UI-проекта

    Частые варианты:

  • smoke: короткий набор проверок “жив ли продукт”
  • regression: расширенный набор регресса
  • ui: всё, что требует браузер
  • auth: тесты авторизации
  • Пример:

    Запуск только smoke:

    Запуск всех кроме регресса:

    Регистрация марок в pytest.ini

    Если вы используете свои марки, их лучше зарегистрировать, чтобы:

  • избежать предупреждений PytestUnknownMarkWarning
  • задокументировать смысл марок для команды
  • Управляющие марки: skip, skipif, xfail

    Они помогают работать с временными ограничениями и известными проблемами.

  • skip — пропустить всегда
  • skipif — пропустить по условию
  • xfailожидаемое падение (известный баг)
  • Документация: Skipping and xfail

    Пример:

    Важно: xfail — это не “спрятать проблему”, а честно зафиксировать известный дефект до его исправления.

    Репорты: как получать результаты, понятные человеку и CI

    Репорт — это представление результатов прогона, которое помогает быстро понять:

  • какие тесты упали
  • где именно упали
  • какие артефакты приложены (скриншоты, логи)
  • Полезные опции запуска Pytest

    | Задача | Команда | |---|---| | Больше деталей в консоли | pytest -vv | | Остановиться после первого падения | pytest -x | | Ограничить число падений | pytest --maxfail=2 | | Запустить по имени теста (частично) | pytest -k "login" |

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

    JUnit XML для CI (встроено в Pytest)

    Большинство CI-систем умеют читать формат JUnit.

    Документация: JUnit XML

    HTML-репорт через плагин pytest-html

    Если нужен один HTML-файл с результатами:

  • проект плагина: pytest-html
  • Установка:

    Запуск:

    Скриншоты при падении теста (артефакт для UI)

    В UI-тестах скриншот в момент падения часто экономит часы.

    Один из практичных подходов: хук pytest_runtest_makereport, который срабатывает после выполнения теста.

    Что здесь происходит:

  • item — объект текущего теста (доступ к имени и аргументам)
  • report.failed — признак падения
  • item.funcargs.get("driver") — попытка получить Selenium-драйвер, если тест его использовал
  • save_screenshot(...) — сохраняем PNG в папку reports/
  • Это не “репорт” само по себе, но это фундамент для нормальной диагностики: тест упал — скриншот есть.

    Если вы используете HTML/Allure-репорты, следующий шаг — прикреплять скриншоты прямо в отчёт, но это лучше делать после того, как базовая структура тестов и фикстур уже стабилизировалась.

    Итог

    Теперь ваш каркас Pytest в UI-проекте умеет:

  • управлять инфраструктурой через фикстуры (драйвер, страницы, подготовка данных)
  • запускать один тест с множеством наборов данных (параметризация)
  • выделять наборы тестов и управлять запуском (марки)
  • отдавать результаты в CI и людям (JUnit XML, HTML-репорт через плагин)
  • Дальше этот фундамент будет расширяться в сторону более “боевого” раннера: разные браузеры, параллельный запуск, более аккуратные ожидания и стабильность UI-тестов.

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

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

    Зачем эта тема нужна в связке Pytest + POM

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

  • договорились об архитектуре Pytest + Selenium + Page Object Model
  • настроили структуру проекта и фикстуры Pytest
  • вынесли ожидания и базовые действия в BasePage
  • Чтобы Page Objects были стабильными и поддерживаемыми, нужно уверенно владеть “механикой” Selenium:

  • как находить элементы (локаторы)
  • как синхронизироваться с интерфейсом (ожидания)
  • как выполнять сложные пользовательские действия (actions)
  • как работать с несколькими окнами/вкладками (windows)
  • Эта статья даёт практический минимум, который вы будете использовать почти в каждом UI-проекте.

    !Где в проекте живут локаторы, ожидания и действия

    Локаторы: как правильно “зацепиться” за элемент

    Локатор в Selenium — это пара (стратегия, значение), по которой WebDriver ищет элемент на странице.

    В Python это обычно выглядит так:

    Официально: Selenium — Locators

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

    | Стратегия | Когда использовать | Пример | |---|---|---| | By.ID | самый стабильный вариант, если есть id | ("#login") как CSS или By.ID, "login" | | By.CSS_SELECTOR | основной рабочий инструмент для большинства случаев | "[data-testid='login']" | | By.XPATH | когда нужно идти по структуре/условиям и CSS неудобен | "//button[normalize-space()='Login']" | | By.NAME | иногда удобно для полей форм | "username" | | By.CLASS_NAME | редко подходит из-за динамических классов | "btn-primary" | | By.LINK_TEXT | для ссылок по точному тексту | "Forgot password" |

    Практические правила выбора локаторов

  • Предпочитайте устойчивые атрибуты: data-testid, data-qa, id.
  • Используйте By.CSS_SELECTOR как базовый вариант: обычно он короче и легче читается.
  • Используйте By.XPATH, когда нужно:
  • - найти по тексту - выбрать элемент относительно другого элемента - выразить условие, которое неудобно для CSS
  • Избегайте “хрупких” селекторов:
  • - длинные цепочки вида div > div > div:nth-child(3) ... - локаторы по динамическим классам (часто генерируются)

    Где хранить локаторы в POM

    Есть два частых варианта (оба рабочие):

  • Локаторы как константы внутри Page Object.
  • Локаторы в отдельном модуле src/locators/..., если их становится много.
  • Пример локаторов внутри страницы:

    find_element и find_elements

  • find_element(...) вернёт один элемент или бросит исключение, если элемент не найден.
  • find_elements(...) вернёт список (возможно пустой), исключения “не найдено” не будет.
  • В POM обычно удобнее прятать прямые вызовы find_element за методами ожиданий в BasePage.

    Ожидания: как синхронизироваться с UI

    UI часто живёт асинхронно:

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

    Официально: Selenium — Waits

    Имплицитные и явные ожидания

  • Имплицитное ожидание (implicitly_wait) добавляет “скрытую задержку” ко всем поискам элементов.
  • Явное ожидание (WebDriverWait + expected_conditions) ждёт конкретное условие.
  • Практика для стабильных автотестов:

  • держать implicitly_wait(0)
  • использовать явные ожидания в BasePage
  • Базовые expected conditions

    Чаще всего достаточно нескольких условий:

  • visibility_of_element_located — элемент есть и видим
  • element_to_be_clickable — элемент можно кликнуть
  • presence_of_element_located — элемент есть в DOM, но может быть невидим
  • invisibility_of_element_located — элемент исчез
  • text_to_be_present_in_element — дождаться текста
  • Пример “правильного” клика через ожидание:

    Почему ожидания лучше держать в BasePage

    Если каждый тест будет сам решать “как ждать”, появятся:

  • разный стиль ожиданий
  • дублирование кода
  • сложная диагностика
  • Поэтому мы централизуем ожидания в BasePage.

    Пример расширенного BasePage с полезными методами:

    Типовые ошибки с ожиданиями

  • time.sleep(...) вместо ожиданий условий.
  • ожидание “видимости”, когда элемент кликается только после того, как стал “кликабельным”.
  • ожидание только появления элемента, хотя нужно дождаться обновления текста.
  • Действия: клики, ввод, скролл и сложные сценарии

    Базовые действия (click, send_keys) покрывают много кейсов, но иногда нужно больше:

  • hover по элементу
  • drag-and-drop
  • выделение текста
  • удержание клавиш
  • Для этого используется Actions API.

    Официально: Selenium — Actions API

    ActionChains: hover и комбинированные действия

    Пример: открыть выпадающее меню наведением, затем кликнуть пункт:

    В POM-архитектуре такие сценарии обычно живут либо в компоненте (HeaderComponent), либо в Page Object.

    Клавиши и спецввод

    Скролл

    Скролл бывает нужен, когда элемент вне viewport и сайт не “подкручивает” сам.

    Практичный вариант через JavaScript:

    Если вы видите ошибки клика типа “element click intercepted”, часто помогает:

  • дождаться кликабельности
  • проскроллить к элементу
  • убедиться, что его не перекрывает фиксированная панель
  • Окна и вкладки: как переключаться и не теряться

    Когда приложение открывает новую вкладку (например, “политика конфиденциальности”, платежный провайдер, внешняя авторизация), тесту нужно уметь:

  • понять, что появилось новое окно
  • переключиться в него
  • выполнить действия
  • вернуться обратно
  • Официально: Selenium — Windows and Tabs

    Базовые понятия: window handle

  • driver.current_window_handle — идентификатор текущего окна.
  • driver.window_handles — список всех открытых окон.
  • Handle — это строковый идентификатор, Selenium использует его для переключения.

    Сценарий: кликнули ссылку, открылась новая вкладка

    Надёжный подход: запомнить текущее окно и дождаться увеличения количества окон.

    Использование в тесте или POM-методе:

    Практические правила:

  • Всегда возвращайтесь обратно в исходное окно.
  • Если закрываете вкладку, сначала close(), затем переключение на оставшееся окно.
  • Если окон может быть больше двух

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

  • по title
  • по URL
  • И также ждать это условие через WebDriverWait.

    Что важно унести в POM-практику

  • Локаторы должны быть устойчивыми и жить в Page Objects/компонентах, а не в тестах.
  • Ожидания должны быть явными и централизованными в BasePage.
  • Сложные действия (hover, комбинации) делаются через Actions API и тоже прячутся в методы страниц/компонентов.
  • Работа с окнами требует дисциплины: запомнили исходное окно, дождались нового, переключились, сделали действия, закрыли, вернулись.
  • Что будет дальше

    Следующий шаг после освоения локаторов/ожиданий/окон — сделать проект ещё более “боевым”:

  • борьба с флаки-тестами (стратегии ожиданий, ретраи на уровне действий)
  • настройка разных браузеров и режимов запуска
  • параллельный прогон и более богатая отчётность
  • 5. Page Object Model: страницы, компоненты и базовые классы

    Page Object Model: страницы, компоненты и базовые классы

    Как эта тема продолжает курс

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

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

  • как проектировать страницы и компоненты
  • где хранить локаторы
  • как устроить базовые классы так, чтобы тесты были короткими, а POM стабильным
  • Полезная теория по паттерну: Martin Fowler — PageObject

    !Схема показывает, как тесты зависят от страниц/компонентов, а те используют базовый слой

    Что такое Page Object в практическом смысле

    Page Object — это класс, который представляет страницу (или крупный экран) приложения и даёт тестам “пользовательский API” вместо прямого Selenium-кода.

    Признаки хорошего Page Object:

  • хранит локаторы и скрывает детали поиска элементов
  • предоставляет методы действий, которые звучат как шаги пользователя: login, search, add_to_cart
  • делает ожидания внутри себя или через BasePage, а не в тестах
  • возвращает другие страницы при навигации, чтобы цепочки сценариев читались естественно
  • Чего Page Object делать не должен:

  • содержать бизнес-assert теста внутри “действий”
  • превращаться в “комбайн” на весь сайт
  • заставлять тесты работать с WebDriverWait, find_element и XPath напрямую
  • Страница и компонент: где граница

    В UI почти всегда есть повторяющиеся части. Поэтому POM удобно делить на два вида сущностей.

    Страница

    Страница описывает целый экран или маршрут приложения.

  • обычно имеет url или способ открыть страницу
  • содержит “сценарные” действия: авторизоваться, найти товар, перейти в корзину
  • может агрегировать компоненты, которые на ней присутствуют
  • Компонент

    Компонент описывает переиспользуемый фрагмент интерфейса.

  • хедер, меню, таблица, модальное окно, уведомления
  • не обязан иметь собственного open(url)
  • часто работает в рамках корня, чтобы не искать элементы по всему DOM
  • Практическое правило:

  • если элемент встречается на многих страницах и имеет своё поведение, это кандидат в компонент
  • если объект соответствует конкретному экрану и навигации, это кандидат в страницу
  • Базовые классы: фундамент стабильности

    В прошлых статьях мы уже использовали BasePage как “точку ожиданий”. Теперь сделаем его более пригодным для реального POM:

  • добавим find и find_all
  • научимся работать с “корнем” для компонентов
  • добавим небольшие безопасные утилиты: is_visible, wait_until_disappears
  • Документация по ожиданиям: Selenium — Waits

    Базовый класс BasePage

    Почему для find используется presence_of_element_located:

  • это универсальная базовая операция “элемент существует в DOM”
  • когда нужно взаимодействовать, мы используем более строгие ожидания visibility или clickable
  • Базовый класс компонента BaseComponent

    Идея компонента: искать элементы не от driver, а от корня компонента, чтобы локаторы были устойчивее и случайно не зацепили “такой же элемент” в другом месте страницы.

    Примечание: здесь ожидание сделано через lambda, потому что expected_conditions по умолчанию работают от driver, а не от root. Это нормальная практика для компонентного POM.

    Локаторы: где и как их держать

    Вы уже видели вариант, когда локаторы живут в классе страницы. Это хороший старт.

    Два рабочих подхода:

  • локаторы внутри Page Object или компонента
  • локаторы в отдельном файле src/locators/..., если их становится много
  • Важно, чтобы тесты локаторов не содержали.

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

  • предпочитать data-testid, data-qa, id
  • CSS как базовый инструмент, XPath только когда он реально удобнее
  • Документация по локаторам: Selenium — Locators

    Проектирование страниц: “пользовательский API” и навигация

    Хороший Page Object обычно включает:

  • методы действий
  • минимальные проверки “страница открыта” через уникальный элемент
  • методы навигации, которые возвращают другой Page Object
  • Пример: страница логина

    Почему полезно разделять login_valid и login_invalid:

  • при успешном логине ожидается переход на другую страницу, и метод возвращает MainPage
  • при неуспешном логине остаёмся на LoginPage, и метод возвращает текущую страницу
  • Тестам проще читать сценарий, а тип возврата подсказывает ожидаемую навигацию.

    Компоненты: переиспользование без дублирования

    Представим, что на большинстве страниц есть хедер с поиском и ссылкой на профиль.

    Компонент Header

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

    Обратите внимание на @property header:

  • компонент создаётся “по требованию”
  • в качестве root передаётся найденный DOM-элемент хедера
  • тестам доступен чистый API: main_page.header.search("...")
  • Как должен выглядеть тест при “здоровом” POM

    Тест не знает про локаторы, ожидания и Selenium-детали. Он описывает сценарий и проверки.

    Если добавить фикстуру login_page, тест станет ещё короче, но смысл POM останется тем же.

    Частые ошибки при внедрении POM

  • Page Object делает assert внутри: тест теряет контроль над проверками и хуже диагностируется
  • методы страниц возвращают None при навигации: сценарий становится менее читаемым
  • компоненты ищут элементы от driver, а не от корня: появляются случайные совпадения локаторов
  • в Page Object кешируют WebElement как поле класса: после перерендера страницы это часто приводит к StaleElementReferenceException
  • локаторы “уплывают” в тесты: при изменении вёрстки ломается много файлов
  • Практичные правила проектирования POM для курса

  • один метод Page Object должен быть понятен без чтения кода, по одному названию
  • ожидания и взаимодействия концентрируются в базовых классах
  • проверки уровня “страница открыта” допустимы как is_opened, но бизнес-проверки остаются в тестах
  • повторяющиеся куски UI выносите в компоненты
  • навигацию оформляйте возвратом других Page Objects
  • Итог

    Теперь у вас есть понятная модель, как строить POM-слой:

  • BasePage как единая точка ожиданий и базовых действий
  • Page Object как API целой страницы
  • Component Object как API переиспользуемого фрагмента
  • На этом фундаменте дальше проще развивать проект в сторону стабильности и удобства: расширять BasePage, добавлять новые компоненты, улучшать диагностику и сокращать тесты до “сценарий + assert”.

    6. Тестовые данные и устойчивость: конфиги, фейки, flaky-тесты

    Тестовые данные и устойчивость: конфиги, фейки, flaky-тесты

    Зачем это нужно в связке Pytest + Selenium + POM

    В предыдущих статьях курса вы построили каркас UI-автопроекта:

  • Pytest управляет запуском, фикстурами и отчётами
  • Selenium управляет браузером
  • POM прячет локаторы, ожидания и действия за “API страниц”
  • Следующая реальная боль, которая появляется уже на первых десятках UI-тестов:

  • тестам нужны данные (пользователи, товары, адреса)
  • тестам нужны настройки (окружения, таймауты, браузеры)
  • тесты начинают падать “через раз” (flaky)
  • Цель этой статьи — показать практичный подход:

  • как хранить конфиги так, чтобы проект был переносимым (локально и в CI)
  • как генерировать тестовые данные фейками и при этом сохранять воспроизводимость
  • как выявлять и уменьшать flaky-тесты, не превращая проект в набор sleep
  • !Карта того, где в проекте живут конфиги, тестовые данные и механизмы устойчивости

    Конфиги: как сделать тесты переносимыми

    Конфиг — это всё, что меняется между окружениями и режимами запуска:

  • базовый URL (dev/stage/prod)
  • таймауты ожиданий
  • режим браузера (headless или нет)
  • учётные данные тестовых пользователей
  • Базовый принцип

    Тесты и Page Objects не должны “знать” про окружение.

  • тест описывает сценарий и проверки
  • POM описывает действия и локаторы
  • конфиги инициализируются в фикстурах и передаются в страницы/компоненты
  • Минимальный settings.py

    Создадим единое место, где лежат значения “по умолчанию”, а переопределение сделаем через переменные окружения.

    Почему это полезно:

  • локально тесты работают без дополнительной настройки
  • в CI можно переопределить параметры безопасно (через секреты/variables)
  • Документация: os — Miscellaneous operating system interfaces

    Прокидываем конфиги в фикстуры Pytest

    Соберём настройки в отдельную фикстуру, чтобы тесты не импортировали settings напрямую.

    Теперь driver и страницы можно настраивать через config.

    Таймауты в одном месте

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

    А в BasePage используем config["timeout"] при создании страниц.

    И создаём страницы так:

    Практичный вариант для курса: либо пробрасывать timeout в каждую страницу, либо сделать фабрику страниц/фикстуру “страница с таймаутом”.

    Переопределение конфигов через опции Pytest

    Переменные окружения удобны в CI, а локально часто удобнее переключать окружение флагом.

    Запуск:

    Документация: Pytest — Adding default options

    Тестовые данные: статические, генерируемые и подготовленные

    В UI-тестах данные обычно приходят из трёх источников:

  • статические данные (заранее созданные аккаунты и сущности)
  • генерируемые данные (фейки: имена, email, адреса)
  • подготовленные данные (созданы перед тестом через API/БД/админку)
  • Статические данные

    Когда подходят:

  • smoke-набор, где важна простота
  • авторизация тестового пользователя во многих сценариях
  • Риски:

  • тесты начинают конфликтовать за один и тот же аккаунт
  • состояние “копится” (накапливаются заказы, настройки, корзина)
  • Практика:

  • иметь несколько тестовых пользователей по ролям
  • перед тестом чистить состояние (cookies/session и при возможности данные на бэкенде)
  • Фейки (генерация данных)

    Фейки уменьшают конфликты и делают тесты независимее: каждый прогон создаёт новый email/имя.

    Популярная библиотека: Faker

    Установка:

    Фикстура с фейковыми данными:

    Почему пароль лучше фиксировать:

  • генератор может случайно создать пароль, который не проходит правила
  • вы будете отлаживать “валидацию пароля”, хотя тест не про это
  • Воспроизводимость: фиксируем seed

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

    Что это даёт:

  • один и тот же тест генерирует одинаковые данные
  • падение проще повторить локально
  • Компромисс:

  • unique-значения при seed могут начать конфликтовать между тестами
  • практичный подход: seed включать только в “режиме отладки” (например, через env/опцию)
  • Подготовленные данные: лучший путь к устойчивости

    Самые устойчивые UI-тесты обычно:

  • создают тестовые данные до UI-шагов (быстро и стабильно)
  • в UI проверяют именно то, что относится к интерфейсу
  • Частый паттерн:

  • Через API создать пользователя/заказ/товар
  • Через UI открыть страницу и проверить отображение
  • Через API удалить созданные данные (cleanup)
  • Даже если в курсе мы фокусируемся на Selenium, принцип важен: UI — самый дорогой и хрупкий слой, не используйте его для массовой подготовки данных, если есть альтернатива.

    Flaky-тесты: что это и почему появляются

    Flaky-тест — тест, который может:

  • пройти на одном прогоне
  • упасть на другом
  • при неизменном коде продукта.

    Типовые причины flaky в UI

  • неправильные ожидания (ждали presence, а нужно было clickable)
  • гонки с анимациями и перерендерами
  • нестабильные локаторы (динамические классы, длинные CSS-цепочки)
  • “протекание состояния” между тестами (общий пользователь, общие данные)
  • зависимость от внешних сервисов (почта, платежи, сторонняя авторизация)
  • параллельный запуск без изоляции тестовых данных
  • Как отличить flaky от реального бага

    Практичный алгоритм:

  • Перезапустите упавший тест без изменений (локально или на CI)
  • Если падение “переезжает” по месту и ошибкам, это сильный сигнал flaky
  • Если падение стабильно воспроизводится, это похоже на реальный дефект или детерминированную проблему теста
  • Устойчивость: приёмы, которые реально работают

    Убираем sleep, ставим ожидания условий

    time.sleep(...) делает тест:

  • медленным
  • нестабильным (на одном окружении “доспали”, на другом — нет)
  • Вместо этого:

  • ожидать кликабельность перед кликом
  • ожидать видимость перед чтением текста
  • ожидать исчезновение лоадера перед следующими шагами
  • Официально: Selenium — Waits

    Централизуем “стабильные” действия в BasePage

    Одна из частых причин flaky — повторение сырых Selenium-действий в разных местах. Улучшайте действия в одном месте.

    Пример: клик с понятным ожиданием и автоскроллом.

    Если в проекте часто встречается ElementClickInterceptedException, такие улучшения окупаются быстро.

    Не кешируем WebElement в Page Object

    После перерендера DOM старый WebElement становится “протухшим”, и Selenium выдаёт StaleElementReferenceException.

    Практика POM:

  • хранить локаторы
  • получать элементы через find/find_visible каждый раз, когда нужно действие
  • Изолируем тесты по данным

    Если два теста меняют один и тот же объект (например, профиль пользователя), они могут ломать друг друга.

    Устойчивые подходы:

  • создавать уникальные сущности на каждый тест (через API или генерацию)
  • иметь пул тестовых пользователей и выдавать уникального на тест (особенно при параллельном запуске)
  • чистить состояние после теста (удаление созданных сущностей)
  • Делайте проверки “с ожиданием”, а не “мгновенные”

    Плохой вариант (часто flaky):

    Хороший вариант: дождаться появления тоста и только потом читать.

    Практика:

  • в POM завести метод wait_success_toast()
  • внутри использовать visibility_of_element_located
  • Ретраи: использовать осторожно и осознанно

    Плагин ретраев может помочь, но он не лечит причину flaky.

    Полезный инструмент: pytest-rerunfailures

    Когда ретраи допустимы:

  • у вас есть внешний нестабильный зависимый сервис
  • вы временно “страхуете” прогон, параллельно исправляя корневую причину
  • Когда ретраи вредны:

  • тест постоянно падает из-за плохих ожиданий
  • тесты начинают проходить “со второго раза”, а вы теряете сигнал о проблеме
  • Диагностика: артефакты при падении

    В статье про Pytest вы уже добавляли скриншоты на падение. Это минимум.

    Что ещё часто добавляют в UI-проектах:

  • HTML страницы на момент падения (driver.page_source в файл)
  • логи браузера (в зависимости от драйвера и настроек)
  • URL и title страницы
  • Пример: сохранить HTML при падении рядом со скриншотом.

    Практичная “политика качества” для UI-набора

    Чтобы проект не деградировал, полезно договориться о правилах.

    Что считать “готовым” тестом

  • нет sleep
  • нет локаторов в тесте
  • ожидания только через BasePage/компоненты
  • тест не зависит от данных, созданных другими тестами
  • Что делать с flaky-тестом

    Один из рабочих подходов:

  • Пометить тест маркой xfail или вынести в отдельный набор “quarantine”
  • Собрать артефакты (скриншот, HTML, логи)
  • Исправить корневую причину (ожидание, локатор, изоляция данных)
  • Вернуть тест в основной прогон
  • Документация: Pytest — Skipping and xfail

    Итог

    Теперь у вашего UI-проекта появляется ещё один слой зрелости:

  • конфиги отделены от тестов и переопределяются через env/CLI
  • тестовые данные можно создавать через фейки и делать их воспроизводимыми
  • flaky-тесты распознаются по причинам и лечатся ожиданиями, изоляцией данных и улучшением базовых действий
  • при падениях сохраняются артефакты, ускоряющие диагностику
  • На следующем этапе развития проекта обычно усиливают инфраструктуру запуска: несколько браузеров, параллельный прогон и более богатые репорты, но фундамент устойчивости всегда начинается с конфигов, данных и дисциплины ожиданий.

    7. Запуск и качество: параллельность, Docker/Selenoid и CI/CD

    Запуск и качество: параллельность, Docker/Selenoid и CI/CD

    Зачем этот слой нужен после Pytest + Selenium + POM

    В прошлых статьях вы собрали рабочий UI-проект:

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

  • ускорить прогоны через параллельность
  • стандартизировать окружение через Docker
  • запускать тесты в контейнерных браузерах через Selenoid
  • встроить прогон в CI/CD, чтобы качество проверялось автоматически
  • !Общая схема, как код, Selenoid и Pytest соединяются в CI

    Параллельный запуск Pytest

    Зачем параллелить UI-тесты

    UI-тесты почти всегда самые медленные. Параллельный запуск помогает:

  • сократить время прогона регресса
  • быстрее получать обратную связь в CI
  • отделить быстрые наборы (smoke) от больших (regression)
  • Ограничения, о которых важно помнить:

  • браузеры тяжёлые по ресурсам
  • тесты должны быть независимыми по данным и состоянию
  • параллельность повышает шанс столкнуться с проблемами окружения, которые в последовательном прогоне не проявлялись
  • pytest-xdist

    Самый популярный способ параллелить Pytest: плагин pytest-xdist.

    Установка:

    Запуск на 4 воркерах:

    Автовыбор количества воркеров:

    Практичные режимы распределения:

  • --dist=loadscope часто удобен, когда вы хотите держать тесты из одного модуля или класса в одном воркере
  • Официальная документация Pytest по плагинам: Plugins.

    Что должно быть готово в проекте для параллельности

    Чтобы параллельный запуск не превратился в хаос, обычно нужно:

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

  • один общий тестовый пользователь, который меняет профиль, корзину или настройки
  • тесты, которые зависят от созданных ранее сущностей
  • использование общих файлов для записи результатов без уникальных имён
  • Практика:

  • генерируйте уникальные данные (email, названия) через Faker
  • если данные создаются заранее, заведите пул пользователей и раздавайте их тестам без пересечений
  • добавляйте в имена артефактов worker id, чтобы параллельные тесты не перетирали файлы
  • Артефакты в параллельном прогоне

    Если вы сохраняете скриншоты и HTML при падении, добавьте идентификатор воркера.

    pytest-xdist прокидывает переменную окружения PYTEST_XDIST_WORKER.

    Пример доработки хука артефактов:

    Docker как способ стабилизировать окружение

    Зачем Docker в UI-автотестах

    Docker даёт повторяемость:

  • одинаковая версия Python и зависимостей
  • одинаковый способ запуска в CI и локально
  • меньше проблем вида у меня проходит, у тебя нет
  • При этом UI-тесты в Docker почти всегда требуют отдельного решения для браузеров:

  • либо браузер ставится внутрь образа (часто тяжело и медленно)
  • либо браузеры запускаются в отдельных контейнерах, а тесты подключаются к ним по сети
  • Во второй модели как раз появляется Selenoid.

    Минимальный Dockerfile для проекта тестов

    Этот Dockerfile упаковывает только тестовый код и зависимости.

    Что важно:

  • тестовый контейнер не обязан содержать браузер
  • браузер можно вынести во внешнюю инфраструктуру (Selenoid, Selenium Grid)
  • Официальная справка по образам Python: Docker Hub python.

    Selenoid: браузеры в контейнерах

    Что такое Selenoid и зачем он нужен

    Selenoid запускает браузеры в контейнерах и отдаёт стандартный интерфейс Selenium Remote WebDriver.

    Это полезно, когда:

  • нужно много параллельных браузеров
  • вы хотите управлять версиями браузеров централизованно
  • вам нужно одинаковое окружение локально и в CI
  • Официальный проект: Selenoid.

    Минимальный docker-compose для Selenoid

    Ниже рабочий стартовый вариант. Он рассчитан на запуск Selenoid в Docker и использование готовых образов браузеров Aerokube.

    Файл browsers.json задаёт доступные версии браузеров:

    Запуск:

    Проверка:

  • статус Selenoid: http://localhost:4444/status
  • UI: http://localhost:8080/
  • Подключение тестов к Remote WebDriver

    Вместо webdriver.Chrome() вы создаёте webdriver.Remote(...).

    Ключевая идея архитектурно:

  • тесты и POM не меняются
  • меняется только фикстура driver
  • Пример с опцией --remote-url.

    Запуск локально с Selenoid:

    Почему добавляют /wd/hub:

  • многие Selenium-серверы исторически ожидают этот путь
  • в некоторых конфигурациях Selenoid он тоже используется
  • Если ваша конфигурация принимает просто http://localhost:4444/, используйте её.

    CI/CD для UI-тестов

    Что именно должен делать CI для UI-автотестов

    Обычно минимальный полезный CI-пайплайн:

  • установить зависимости
  • поднять инфраструктуру браузеров (Selenoid)
  • запустить тесты с нужными опциями
  • сохранить артефакты
  • показать результаты в интерфейсе CI
  • Важно разделять цели:

  • pull request checks должны быть быстрыми (smoke)
  • ночной регресс может быть тяжёлым и параллельным
  • Пример GitHub Actions workflow

    Официальная документация: GitHub Actions.

    Ниже пример пайплайна, который:

  • поднимает Selenoid через Docker Compose
  • запускает UI-тесты параллельно
  • сохраняет JUnit XML и папку reports/
  • Практичные замечания:

  • if: always() гарантирует выгрузку артефактов даже при падениях
  • BASE_URL и секреты лучше прокидывать через переменные окружения и секреты GitHub
  • Отбор тестов в CI: smoke против regression

    В статье про Pytest вы регистрировали марки. Это напрямую используется в CI.

    Пример:

  • на pull request гоняем только smoke:
  • ночью гоняем регресс:
  • Так CI остаётся быстрым там, где важна скорость, и полным там, где важна глубина.

    Практики качества для стабильных прогонов

    Изоляция данных важнее, чем количество ретраев

    Если тесты конфликтуют по данным, параллельность только усилит проблему.

    Проверка готовности набора к параллельности:

  • тест создаёт сущности с уникальными ключами
  • тест удаляет сущности или использует одноразовые данные
  • тест не зависит от порядка выполнения
  • Ретраи использовать как временную страховку

    Если вам нужно временно снизить красноту прогонов, можно использовать pytest-rerunfailures.

    Но правило качества:

  • ретраи не заменяют исправление ожиданий, локаторов и изоляции данных
  • Карантин для flaky-тестов

    Практичный способ не блокировать релизы:

  • flaky-тесты помечаются маркой quarantine
  • основной пайплайн запускает not quarantine
  • карантинный набор запускается отдельно и используется для лечения нестабильности
  • Пример регистрации и использования:

    Запуск без карантина:

    Итог

    Теперь ваш проект готов к “боевому” режиму:

  • Pytest ускоряется через параллельный запуск pytest-xdist
  • Docker даёт воспроизводимость окружения
  • Selenoid позволяет держать браузеры в контейнерах и масштабировать параллельность
  • CI/CD запускает тесты автоматически, сохраняет артефакты и помогает держать качество
  • Следующий практический шаг после внедрения CI обычно один из двух:

  • усилить отчётность и удобство разбора падений (например, Allure)
  • оптимизировать стабильность на уровне базовых действий и тестовых данных, чтобы параллельные прогоны были предсказуемыми