Appium и Selenium WebDriver с Python: Как Создать Фреймворк с Нуля?

Автоматизация тестирования стала неотъемлемой частью цикла разработки программного обеспечения. Appium и Selenium WebDriver являются ключевыми инструментами в этой области, а Python – одним из самых популярных языков для их использования. Этот гайд проведет вас через процесс создания гибкого и масштабируемого фреймворка автоматизации с нуля.

Что такое Appium и Selenium WebDriver: Обзор технологий

  • Selenium WebDriver – это де-факто стандарт для автоматизации веб-браузеров. Он предоставляет API для взаимодействия с веб-страницами так, как это делает пользователь: клики, ввод текста, навигация и проверка элементов. Selenium поддерживает множество браузеров (Chrome, Firefox, Safari, Edge) и языков программирования.
  • Appium – это инструмент с открытым исходным кодом для автоматизации нативных, гибридных и мобильных веб-приложений на платформах iOS, Android и Windows. Appium использует WebDriver API, расширяя его для нужд мобильной автоматизации. Ключевое преимущество Appium – возможность использовать один и тот же API и часто тот же код для автоматизации на разных платформах.

Преимущества использования Python для автоматизации тестирования

Python завоевал популярность в автоматизации благодаря ряду преимуществ:

  • Простота и читаемость: Синтаксис Python близок к естественному языку, что упрощает написание и поддержку кода.
  • Обширная экосистема: Огромное количество библиотек (включая selenium, Appium-Python-Client, pytest, requests) ускоряет разработку.
  • Кроссплатформенность: Python работает на Windows, macOS и Linux.
  • Сильное сообщество: Легко найти документацию, примеры и помощь.
  • Интеграция: Python легко интегрируется с другими инструментами и системами (CI/CD, системы отчетов, API).

Необходимые инструменты и установка: Python, Appium, Selenium, IDE

Для начала работы вам понадобятся:

  1. Python: Версия 3.8 или выше.
  2. pip: Менеджер пакетов Python (обычно устанавливается вместе с Python).
  3. Appium Server: Графическое приложение или версия для командной строки (Node.js).
  4. Appium Inspector: Инструмент для инспекции элементов мобильных приложений.
  5. Selenium WebDriver: Библиотека Python (selenium).
  6. Appium Python Client: Библиотека Python (Appium-Python-Client).
  7. WebDriver Бинарные файлы: ChromeDriver, GeckoDriver и т.д., соответствующие версиям ваших браузеров.
  8. IDE (Интегрированная среда разработки): PyCharm, VS Code или любая другая по вашему выбору.
  9. Инструменты для мобильной разработки (опционально, для Appium): Android SDK (для Android), Xcode (для iOS).

Настройка окружения для работы с Appium и Selenium

Правильная настройка окружения – фундамент успешной автоматизации.

Установка Python и необходимых библиотек (pip)

Убедитесь, что Python и pip установлены и добавлены в PATH. Затем установите основные библиотеки:

pip install selenium Appium-Python-Client pytest

Рекомендуется использовать виртуальные окружения (venv) для изоляции зависимостей проекта:

python -m venv venv
source venv/bin/activate  # Linux/macOS
# или
.\venv\Scripts\activate # Windows

pip install selenium Appium-Python-Client pytest

Настройка Appium Server: Скачивание, установка, конфигурация

Appium Server можно установить несколькими способами:

  • Appium Desktop: Скачайте с официального сайта Appium. Предоставляет графический интерфейс и встроенный Inspector.
  • npm: Если у вас установлен Node.js и npm:
    bash
    npm install -g appium
    # Запуск:
    appium

После установки запустите Appium Server. Он будет ожидать подключения от вашего скрипта на стандартном порту (обычно 4723). Для мобильной автоматизации также потребуются appium-doctor для проверки конфигурации и соответствующие драйверы (например, uiautomator2 для Android, xcuitest для iOS), которые можно установить через Appium:

appium driver install uiautomator2
appium driver install xcuitest

Установка Selenium WebDriver и драйверов браузеров (ChromeDriver, GeckoDriver и т.д.)

Библиотека selenium уже установлена через pip. Теперь необходимо скачать исполняемые файлы драйверов для браузеров, которые вы планируете автоматизировать.

Распакуйте архивы и поместите исполняемые файлы драйверов в директорию, которая включена в системную переменную PATH, или указывайте путь к ним при инициализации WebDriver.

Современные версии Selenium (4.6+) включают Selenium Manager, который автоматически скачивает и управляет драйверами, что значительно упрощает процесс.

Настройка IDE (PyCharm, VS Code) для разработки и отладки

Настройте вашу IDE для работы с Python проектом:

  1. Интерпретатор: Укажите путь к интерпретатору Python в вашем виртуальном окружении.
  2. Плагины: Установите плагины для Python, поддержки pytest и т.д.
  3. Отладчик: Научитесь использовать встроенный отладчик для пошагового выполнения кода и анализа переменных.
  4. Линтеры/Форматеры: Настройте инструменты вроде flake8 или black для поддержания качества кода.

Создание базового фреймворка автоматизации тестирования

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

Определение структуры фреймворка: Модули, папки, файлы

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

my_test_framework/
├── venv/                  # Виртуальное окружение
├── configs/               # Файлы конфигурации (urls, credentials, capabilities)
│   ├── config.json
│   └── capabilities.json
├── core/                  # Ядро фреймворка
│   ├── __init__.py
│   ├── base_page.py       # Базовый класс для Page Objects
│   ├── driver_factory.py  # Фабрика для создания экземпляров WebDriver/AppiumDriver
│   └── utils/             # Вспомогательные утилиты
│       ├── __init__.py
│       └── logger.py        # Настройка логирования
│       └── wait_utils.py    # Пользовательские ожидания
├── pages/                 # Page Objects для веб и мобайл
│   ├── __init__.py
│   ├── web/
│   │   ├── __init__.py
│   │   └── login_page.py
│   │   └── search_page.py
│   └── mobile/
│       ├── __init__.py
│       └── main_screen.py
├── tests/                 # Тестовые сценарии
│   ├── __init__.py
│   ├── conftest.py        # Настройки и фикстуры pytest
│   ├── web/
│   │   ├── __init__.py
│   │   └── test_login.py
│   └── mobile/
│       ├── __init__.py
│       └── test_navigation.py
├── reports/               # Папка для отчетов о тестировании
├── requirements.txt       # Список зависимостей проекта
└── pytest.ini             # Конфигурация pytest

Разработка базовых классов: Page Object Model (POM)

POM – это шаблон проектирования, который инкапсулирует взаимодействие с UI в объекты страниц. Это повышает читаемость и упрощает поддержку тестов.

Создадим базовый класс BasePage:

# core/base_page.py
from typing import Tuple, List
from selenium.webdriver.remote.webdriver import WebDriver
from selenium.webdriver.remote.webelement import WebElement
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.common.exceptions import TimeoutException

class BasePage:
    """Базовый класс для всех Page Objects."""

    def __init__(self, driver: WebDriver, timeout: int = 10):
        """Инициализатор BasePage.

        Args:
            driver: Экземпляр WebDriver или AppiumDriver.
            timeout: Максимальное время ожидания элемента (в секундах).
        """
        self.driver = driver
        self.wait = WebDriverWait(driver, timeout)

    def find_element(self, locator: Tuple[str, str]) -> WebElement:
        """Находит один видимый элемент по локатору.

        Args:
            locator: Кортеж (By strategy, selector). Например, (By.ID, 'element_id').

        Returns:
            Найденный WebElement.

        Raises:
            TimeoutException: Если элемент не найден или не виден за указанное время.
        """
        try:
            return self.wait.until(EC.visibility_of_element_located(locator))
        except TimeoutException:
            # Можно добавить логирование или скриншот здесь
            raise TimeoutException(f"Элемент не найден по локатору {locator} за {self.wait._timeout} секунд")

    def find_elements(self, locator: Tuple[str, str]) -> List[WebElement]:
        """Находит все видимые элементы по локатору.

        Args:
            locator: Кортеж (By strategy, selector).

        Returns:
            Список найденных WebElement.
        """
        return self.wait.until(EC.visibility_of_all_elements_located(locator))

    def click_element(self, locator: Tuple[str, str]) -> None:
        """Находит элемент и кликает по нему.

        Args:
            locator: Кортеж локатора.
        """
        element = self.find_element(locator)
        element.click()

    def send_keys_to_element(self, locator: Tuple[str, str], text: str) -> None:
        """Находит элемент и вводит в него текст.

        Args:
            locator: Кортеж локатора.
            text: Текст для ввода.
        """
        element = self.find_element(locator)
        element.clear()
        element.send_keys(text)

    def get_element_text(self, locator: Tuple[str, str]) -> str:
        """Получает текст видимого элемента.

        Args:
            locator: Кортеж локатора.

        Returns:
            Текст элемента.
        """
        element = self.find_element(locator)
        return element.text

    def is_element_displayed(self, locator: Tuple[str, str]) -> bool:
        """Проверяет, отображается ли элемент на странице.

        Args:
            locator: Кортеж локатора.

        Returns:
            True, если элемент отображается, иначе False.
        """
        try:
            return self.find_element(locator).is_displayed()
        except TimeoutException:
            return False
Реклама

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

# pages/web/login_page.py
from selenium.webdriver.common.by import By
from core.base_page import BasePage

class LoginPage(BasePage):
    """Page Object для страницы входа."""

    # Локаторы элементов
    USERNAME_INPUT = (By.ID, 'username')
    PASSWORD_INPUT = (By.ID, 'password')
    LOGIN_BUTTON = (By.XPATH, '//button[@type="submit"]')
    ERROR_MESSAGE = (By.CSS_SELECTOR, '.error-message')

    def __init__(self, driver):
        super().__init__(driver)
        self.driver.get("https://example.com/login") # Пример URL

    def login(self, username: str, password: str) -> None:
        """Выполняет вход в систему.

        Args:
            username: Имя пользователя.
            password: Пароль.
        """
        self.send_keys_to_element(self.USERNAME_INPUT, username)
        self.send_keys_to_element(self.PASSWORD_INPUT, password)
        self.click_element(self.LOGIN_BUTTON)

    def get_error_message(self) -> str:
        """Получает текст сообщения об ошибке.

        Returns:
            Текст сообщения об ошибке.
        """
        return self.get_element_text(self.ERROR_MESSAGE)

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

  • Ожидания: Используйте явные ожидания (WebDriverWait) как показано в BasePage, вместо неявных (driver.implicitly_wait) или time.sleep(). Можно создать кастомные expected_conditions.
  • Скриншоты: Реализуйте функцию для снятия скриншотов при падении тестов. Это можно сделать в обработчиках исключений или в pytest хуках.
  • Логирование: Настройте модуль logging для записи информации о ходе выполнения тестов, ошибках и действиях.
# core/utils/logger.py
import logging
import sys

def setup_logger(name: str = 'test_framework', level: int = logging.INFO) -> logging.Logger:
    """Настраивает и возвращает логгер.

    Args:
        name: Имя логгера.
        level: Уровень логирования (e.g., logging.INFO, logging.DEBUG).

    Returns:
        Настроенный экземпляр логгера.
    """
    logger = logging.getLogger(name)
    if not logger.handlers:
        logger.setLevel(level)
        formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')

        # Вывод в консоль
        ch = logging.StreamHandler(sys.stdout)
        ch.setFormatter(formatter)
        logger.addHandler(ch)

        # Можно добавить вывод в файл
        # fh = logging.FileHandler('framework.log')
        # fh.setFormatter(formatter)
        # logger.addHandler(fh)

    return logger

# Пример использования:
# from core.utils.logger import setup_logger
# logger = setup_logger()
# logger.info("Фреймворк запущен")

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

Храните конфигурационные данные (URL, таймауты, пути) и тестовые данные (учетные записи, поисковые запросы) отдельно от кода тестов. Используйте форматы JSON, YAML или .env файлы.

// configs/config.json
{
  "base_url": "https://your-app-url.com",
  "default_timeout": 15,
  "browser": "chrome"
}
// configs/capabilities.json (Пример для Appium Android)
{
  "platformName": "Android",
  "appium:platformVersion": "11.0",
  "appium:deviceName": "Android Emulator",
  "appium:app": "/path/to/your/app.apk",
  "appium:automationName": "UiAutomator2",
  "appium:noReset": false
}

Читайте конфигурацию при старте фреймворка или тестов.

Написание и запуск первых тестов

Теперь, когда основа фреймворка готова, можно писать тесты.

Разработка тестовых сценариев с использованием Appium и Selenium

Тесты должны быть атомарными, независимыми и проверять конкретную функциональность. Используйте Page Objects для взаимодействия с UI.

Взаимодействие с элементами UI: поиск, ввод данных, клики

Методы для взаимодействия уже реализованы в BasePage. Тесты вызывают методы соответствующих Page Objects.

Использование unittest или pytest для запуска тестов

pytest является более современным и гибким фреймворком для тестирования в Python. Он предоставляет фикстуры, параметризацию, мощные ассерты и плагины.

Создайте файл conftest.py для общих фикстур, например, для инициализации и закрытия драйвера:

# tests/conftest.py
import pytest
import json
from selenium import webdriver
from appium import webdriver as appium_webdriver
from typing import Dict, Any

# Глобальная переменная для драйвера (или передача через фикстуру)
# driver = None

@pytest.fixture(scope="session")
def config() -> Dict[str, Any]:
    """Фикстура для загрузки конфигурации."""
    with open("configs/config.json") as f:
        return json.load(f)

@pytest.fixture(scope="function") # или 'session' для одного драйвера на сессию
def driver(config: Dict[str, Any]) -> webdriver.Remote:
    """Фикстура для инициализации и закрытия WebDriver/AppiumDriver."""
    driver_instance = None
    browser_name = config.get("browser", "chrome").lower()
    is_mobile = config.get("platformName") is not None # Пример определения типа

    if is_mobile:
        # Настройка Appium
        with open("configs/capabilities.json") as f:
            caps = json.load(f)
        appium_server_url = config.get("appium_url", "http://localhost:4723/wd/hub")
        driver_instance = appium_webdriver.Remote(appium_server_url, caps)
    else:
        # Настройка Selenium WebDriver
        if browser_name == "chrome":
            options = webdriver.ChromeOptions()
            # options.add_argument("--headless") # Опционально для headless режима
            driver_instance = webdriver.Chrome(options=options)
        elif browser_name == "firefox":
            options = webdriver.FirefoxOptions()
            driver_instance = webdriver.Firefox(options=options)
        else:
            raise ValueError(f"Неподдерживаемый браузер: {browser_name}")

        driver_instance.maximize_window()
        driver_instance.get(config["base_url"])

    yield driver_instance # Предоставляем драйвер тесту

    # Код закрытия после выполнения теста
    if driver_instance:
        driver_instance.quit()

Пример теста с использованием pytest и Page Objects:

# tests/web/test_login.py
import pytest
from pages.web.login_page import LoginPage
# from pages.web.dashboard_page import DashboardPage # Предполагаемая страница после логина

@pytest.mark.usefixtures("driver") # Используем фикстуру driver из conftest.py
class TestLogin:

    def test_successful_login(self, driver):
        """Тест успешного входа в систему."""
        login_page = LoginPage(driver)
        login_page.login("valid_user", "valid_password")

        # Добавить проверку перехода на нужную страницу или появления элемента
        # dashboard_page = DashboardPage(driver)
        # assert dashboard_page.is_user_avatar_displayed(), "Аватар пользователя не отображается после входа"
        assert "dashboard" in driver.current_url # Простая проверка URL

    def test_invalid_login(self, driver):
        """Тест входа с неверными учетными данными."""
        login_page = LoginPage(driver)
        login_page.login("invalid_user", "wrong_password")

        error_message = login_page.get_error_message()
        assert "Invalid credentials" in error_message, "Неверное сообщение об ошибке"
        assert "login" in driver.current_url # Убедиться, что остались на странице логина

Запуск тестов из командной строки:

pytest tests/web/test_login.py
# или запустить все тесты
pytest

Анализ результатов тестирования и генерация отчетов

pytest предоставляет базовый вывод результатов. Для более детальных отчетов используйте плагины, такие как pytest-html или интегрируйтесь с Allure Report (allure-pytest).

pip install pytest-html
pytest --html=reports/report.html
pip install allure-pytest
pytest --alluredir=reports/allure-results
allure generate reports/allure-results -o reports/allure-report --clean
allure open reports/allure-report

Расширение и улучшение фреймворка

Базовый фреймворк – это только начало. Его можно и нужно развивать.

Интеграция с CI/CD системами (Jenkins, GitLab CI)

Настройте автоматический запуск тестов в CI/CD пайплайне при каждом коммите или по расписанию. Это обеспечивает быструю обратную связь о состоянии приложения.

Параллельный запуск тестов для ускорения выполнения

Используйте pytest-xdist для параллельного запуска тестов на нескольких процессах или машинах. Это критически важно для больших наборов тестов.

pip install pytest-xdist
pytest -n auto # Запустить на количестве ядер CPU
pytest -n 4  # Запустить в 4 потока

Требуется адаптация фикстуры driver для создания независимых экземпляров браузера для каждого потока.

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

Расширьте driver_factory или фикстуру driver для поддержки различных браузеров (Chrome, Firefox, Safari, Edge) и мобильных платформ/устройств (Android/iOS, разные версии ОС, эмуляторы/реальные устройства). Параметры можно передавать через командную строку или файлы конфигурации.

Обработка исключений и повторные попытки выполнения тестов

  • Надежность локаторов: Используйте уникальные и стабильные локаторы (ID, data-testid).
  • Повторные попытки: Используйте плагин pytest-rerunfailures для автоматического перезапуска упавших тестов, что помогает справиться с нестабильностью окружения.
  • Грамотная обработка исключений: Добавляйте блоки try...except в базовые методы для логирования ошибок, снятия скриншотов перед падением.

Создание надежного фреймворка автоматизации – это итеративный процесс, требующий постоянного улучшения и адаптации под нужды проекта. Представленная структура и примеры кода служат прочной основой для построения эффективной системы автоматизированного тестирования с использованием Python, Selenium и Appium.


Добавить комментарий