1 В избранное 0 Ответвления 0

OSCHINA-MIRROR/tianjianchao-TestCodeUI

В этом репозитории не указан файл с открытой лицензией (LICENSE). При использовании обратитесь к конкретному описанию проекта и его зависимостям в коде.
Клонировать/Скачать
Внести вклад в разработку кода
Синхронизировать код
Отмена
Подсказка: Поскольку Git не поддерживает пустые директории, создание директории приведёт к созданию пустого файла .keep.
Loading...
README.md

Python+Selenium+Pytest+PO+Allure+DDT+Log для реализации автоматизированного тестирования веб-интерфейса

Введение

Python: язык программирования

Pytest: независимый, полноценный фреймворк для юнит-тестирования на Python

Selenium: инструмент для тестирования веб-приложений

Allure: отображение отчетов тестирования

DDT: управление данными

1. Предварительные условия

1. Установка среды разработки Python

1.1 Интерпретатор Python Версия 3.8.0

1.2 Интегрированная среда разработки PyCharm Профессиональная версия

2. Загрузка драйвера браузера

Загрузите драйвер браузера, версия драйвера должна совпадать с версией браузера.

Ссылки для загрузки:

Chrome: http://npm.taobao.org/mirrors/chromedriver/ Firefox: https://github.com/mozilla/geckodriver/releases Edge: https://developer.microsoft.com/en-us/microsoft-edge/tools/webdriver/ Safari: https://webkit.org/blog/6900/webdriver-support-in-safari-10/

  • Добавьте драйвер браузера в переменную окружения python: path
  • Или поместите chromedriver.exe в ту же директорию, что и python.exe
  • Также можно указать путь к Chromedriver при запуске В данном проекте драйвер chromedriver.exe помещается в директорию driver проекта - фиксированная директория.

Примечание: Просмотр версии драйвера браузера

1. Нажмите Ctrl + F12 для открытия окна элементов
2. Нажмите Console и введите 'navigator.userAgent'. Обратите внимание на регистр букв.
```3. Установка Allure

1) Загрузите Allure, ссылка для загрузки [https://github.com/allure-framework/allure2/releases](https://github.com/allure-framework/allure2/releases)
2) Распакуйте на локальном компьютере (этот каталог будет использоваться в дальнейшем, не трогайте его), настройте переменную окружения (добавьте каталог `bin` из каталога `allure` в переменную окружения `path` системы)
См. блог "[Обзор фреймворка Allure](https://blog.csdn.net/weixin_59868574/article/details/135502984)" [https://blog.csdn.net/weixin_59868574/article/details/135502984](https://blog.csdn.net/weixin_59868574/article/details/135502984).

4. Установка сторонних библиотек

Создайте файл `requirements.txt` в проекте, введите следующие данные, затем установите файл `requirements.txt` или установите каждую библиотеку отдельно

```python
pytest==7.4.2
selenium==3.141.0
urllib3==1.26.18
PySocks~=1.7.1
PyYAML~=6.0.1
psutil~=5.9.7
allure-pytest~=2.13.2

2. Общий каталог тестового фреймворка

3. Классы инструментов тестирования utils

3.1 Управление временем timer.py

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

Создайте модуль timer.py в каталоге utils

"""
@Файл    : timer.py
@Автор   : TJC
@Дата    : 2023/7/10 
@Описание: Управление строками времени
"""
import time
import datetime
from functools import wraps


def timestamp():
    """Таймстамп"""
    return time.time()


def dt_strftime(fmt="%Y%m"):
    """
    Форматирование даты в datetime формате
    :param fmt "%Y%m%d %H%M%S"
    """
    return datetime.datetime.now().strftime(fmt)


def sleep(seconds=1.0):
    """Время ожидания"""
    time.sleep(seconds)


def running_time(func):
    """Время выполнения функции"""
``````markdown
## 3.1 Функции времени time_util.py

Для тестирования скриптов требуется функциональность времени, поэтому создаем модуль `time_util` в директории `utils`. Включенный код представлен ниже.

```python
def timestamp():
    """Временная метка"""
    return time.time()


def dt_strftime(fmt="%Y%m"):
    """
    Форматирование времени с помощью datetime
    :param fmt "%Y%m%d %H%M%S"
    """
    return datetime.datetime.now().strftime(fmt)


def sleep(seconds=1.0):
    """Засыпание на время"""
    time.sleep(seconds)


def running_time(func):
    """Время выполнения функции"""

    @wraps(func)
    def wrapper(*args, **kwargs):
        start = timestamp()
        res = func(*args, **kwargs)
        print("Проверка элемента завершена! Время выполнения %.3f секунд!" % (timestamp() - start))
        return res

    return wrapper


if __name__ == '__main__':
    print(dt_strftime("%Y%m%d %H%M%S"))

3.2 Управление логами logger.py

Для тестовых скриптов требуется записывать выполнение логов, поэтому создаем модуль управления логами logger в директории utils. Включенный код представлен ниже.

"""
@File    : logger.py
@Author  : TJC
@Date    : 2023/7/10
@Desc    : Модуль управления логами
"""

import logging
from logging.handlers import RotatingFileHandler, TimedRotatingFileHandler
from config.config import cm


class Log:
    def __init__(self, seg):

        self.seg = seg

        # Создаем логгер
        self.logger = logging.getLogger()
        if not self.logger.handlers:
            self.logger.setLevel(logging.DEBUG)

            # Устанавливаем размер файла логов в 10МБ
            max_bytes = 10 * 1024 * 1024
            # Устанавливаем максимальное количество файлов для хранения в 50
            backup_count = 50

Тестовые сценарии требуют записи логов выполнения, поэтому управление логами включено в модуль, который может быть вызван другими модулями. В каталоге utils создается новый модуль logger.py, содержащий следующий код:python """ @File : logger.py @Author : TJC @Date : 2023/7/10 @Desc : Модуль управления логами """

import logging from logging.handlers import RotatingFileHandler, TimedRotatingFileHandler from config.config import cm

class Log: def init(self, seg):

    self.seg = seg

    # Создаем логгер
    self.logger = logging.getLogger()
    if not self.logger.handlers:
        self.logger.setLevel(logging.DEBUG)

        # Определяем размер файла логов как 10МБ
        max_bytes = 10 * 1024 * 1024
        # Определяем количество файлов логов для хранения как 50
        backup_count = 50

        # Создаем обработчик лог-файлов, правила нумерации резервных копий по умолчанию — добавление цифры в конце имени файла, чем больше цифра, тем старше время создания файла
        if self.seg == "time":
            fh = TimedRotatingFileHandler(cm.log_file, when='D', backupCount=backup_count, encoding='utf-8')
            """
            :param: when
                    'S': каждую секунду
                    'M': каждую минуту
                    'H': каждый час
                    'D': каждый день (значение по умолчанию)
                    'W0' ~ 'W6': каждую неделю (0 — понедельник, 1 — вторник и т.д.)
                    'midnight': каждый день в полночь (равнозначен 'D')
            :param: interval  по умолчанию 1, 1D — один день
            """
            fh.setLevel(logging.INFO)
        else:
            fh = RotatingFileHandler(cm.log_file, maxBytes=max_bytes, backupCount=backup_count, encoding='utf-8')
            fh.setLevel(logging.INFO)
Создайте обработчик вывода в консоль
ch = logging.StreamHandler()
ch.setLevel(logging.INFO)

# Определите формат вывода
formatter = logging.Formatter(self.fmt)
fh.setFormatter(formatter)
ch.setFormatter(formatter)
```# Добавьте обработчики
self.logger.addHandler(fh)
self.logger.addHandler(ch)

@property
def fmt(self):
    return '%(levelname)s\t%(asctime)s\t[%(filename)s:%(lineno)d]\t%(message)s'

# Разделение лог-файлов по времени, можно изменить параметр when, рекомендовано использовать в скриптах для тестирования производительности
# log = Log('time').logger
# Разделение лог-файлов по размеру
log = Log('size').logger

Четвертый раздел: конфигурация папки config

4.1 Общие настройки config.py

Все пути к файлам и директориям объединены в одном месте и могут быть использованы другими модулями. При добавлении новых файлов или директорий, они должны быть добавлены здесь. Создайте новый модуль config.py в папке config, код следующий:

"""
@File    : config.py
@Author  : TJC
@Date    : 2023/7/10
@Desc    : Пути к файлам и директориям проекта
"""

import os
from utils.timer import dt_strftime


class ConfigManager:
    """
    Управление директориями и файлами проекта
    """

    DIR_CURR = os.path.abspath(__file__)

    DIR_CONF = os.path.dirname(os.path.abspath(__file__))

    # Путь к проекту
    DIR_BASE = os.path.dirname(DIR_CONF)

    DIR_COMM = os.path.join(DIR_BASE, "common")

    DIR_UTIL = os.path.join(DIR_BASE, "utils")

    # Путь к объектам страниц
    DIR_PAGE = os.path.join(DIR_BASE, 'page_object')

    # Путь к элементам страниц
    DIR_ELEMENT = os.path.join(DIR_BASE, "page_element")

    DIR_DRIVER = os.path.join(DIR_BASE, "driver")

    @property
    def web_ini_file(self):
        """Файл конфигурации web"""
        ini_file = os.path.join(self.DIR_CONF, 'web_cfg.ini')
        if not os.path.exists(ini_file):
            raise FileNotFoundError("Конфигурационный файл %s отсутствует!" % ini_file)
        return ini_file
``````python
    @property
    def dir_report_json(self):
        """Директория отчета Allure JSON файлы"""
        report_dir = os.path.join(self.DIR_BASE, 'report')
        os.makedirs(report_dir, exist_ok=True)
        json_dir = os.path.join(report_dir, 'json')
        os.makedirs(json_dir, exist_ok=True)
        return json_dir


    @property
    def dir_report_html(self):
        """Директория отчета Allure в формате HTML"""
        report_dir = os.path.join(self.DIR_BASE, 'report')
        os.makedirs(report_dir, exist_ok=True)
        html_dir = os.path.join(report_dir, 'html')
        os.makedirs(html_dir, exist_ok=True)
        return html_dir


    @property
    def dir_log(self):
        """Директория логов"""
        log_dir = os.path.join(self.DIR_BASE, 'logs')
        os.makedirs(log_dir, exist_ok=True)
        return log_dir


    @property
    def log_file(self):
        """Файл логов"""
        return os.path.join(self.dir_log, '{}.log'.format(dt_strftime()))


    @property
    def dir_img(self):
        """Директория скриншотов"""
        img_dir = os.path.join(self.dir_log, 'images')
        os.makedirs(img_dir, exist_ok=True)
        return img_dir


    @property
    def img_file(self):
        """Файл скриншота"""
        return os.path.join(self.dir_img, '{}.png'.format(dt_strftime('%Y%m%d%H%M%S')))


    @property
    def dir_testdata(self):
        """Директория тестовых данных"""
        test_data_dir = os.path.join(self.DIR_BASE, 'testdata')
        os.makedirs(test_data_dir, exist_ok=True)
        return test_data_dir


    @property
    def dir_web_testdata(self):
        """Директория тестовых данных-web"""
        test_data_dir = os.path.join(self.dir_testdata, 'web')
        os.makedirs(test_data_dir, exist_ok=True)
        return test_data_dir


    @property
    def test_web_yaml_file(self):
        """Файл тестовых данных YAML"""
        yaml_file = os.path.join(self.dir_web_testdata, 'test_data.yaml')
        if not os.path.exists(yaml_file):
            raise FileNotFoundError("Файл тестовых данных %s не существует!" % yaml_file)
        return yaml_file
``````markdown
### 4.2 конфигурационный файл web_cfg.ini

Создайте файл `web_cfg.ini` в директории `config` (название файла должно совпадать с названием, используемым в `config.py`). Этот файл предназначен для хранения параметров для веб-тестирования, пример представлен ниже:

web_cfg.ini

[host] HOST = http://IP/index.action [driver]

Имя процесса драйвера

DRIVER_PROCESS = chromedriver.exe


## 5. Общие тестовые библиотеки common

### 5.1 Общие методы common.py

Передовые и последующие действия pytest-фреймворка, автоматически вызываются при выполнении. В директории `common` создайте модуль `common.py`, здесь передовые и последующие действия представляют собой только логирование, что облегчает просмотр логов, код представлен ниже:

""" @File : common.py @Author : TJC @Date : 2023/7/10 @Desc : Передовые и последующие действия для выполнения тестовых случаев, pytest-фреймворк """ from utils.logger import log

class Common:

@staticmethod
def setup_class():
    log.info('------- начало модуля')

```python
@property
def test_web_json_file(self):
    """файл тестовых данных.json"""
    json_file = os.path.join(self.dir_web_testdata, 'test_data.json')
    if not os.path.exists(json_file):
        raise FileNotFoundError("Файл тестовых данных %s не существует!" % json_file)
    return json_file

@property
def web_driver(self):
    """драйвер браузера"""
    os.makedirs(self.DIR_DRIVER, exist_ok=True)
    driver = os.path.join(self.DIR_DRIVER, 'chromedriver.exe')
    if not os.path.exists(driver):
        raise FileNotFoundError("Драйвер браузера %s не существует!" % driver)
    return driver
``````markdown
### 5.2 Чтение конфигурационных файлов read_cfg.py

Конфигурационные файлы используются в формате ini. Предоставляется метод для чтения ini-файлов. В директории `common` создайте файл `read_cfg.py`. Код представлен ниже:

```python
"""
@File    :read_cfg.py
@Author  : TJC
@Date    : 2023/7/10
@Desc    :Обертка для чтения конфигурационных файлов ini
"""

import configparser
from config.config import cm


# Определите фактические значения
HOST = 'host'
DRIVER = 'driver'

class ReadConfig:
    """Класс для работы с ini-конфигурационным файлом"""

    def __init__(self, ini_file):
        self.file = ini_file
        self.config = configparser.RawConfigParser()  # При наличии символов % используйте Raw для чтения, чтобы не анализировать значения в конфигурационном файле
        self.config.read(self.file, encoding='utf-8')

    def _get(self, section, option):
        """Получение значения из конфигурационного файла"""
        return self.config.get(section, option)

    def _set(self, section, option, value):
        """Обновление значения в конфигурационном файле"""
        self.config.set(section, option, value)
        with open(cm.ini_file, 'w') as f:
            self.config.write(f)

    @property
    def web_url(self):
        return self._get(HOST, 'HOST')

    @property
    def web_driver_process(self):
        return self._get(DRIVER, 'DRIVER_PROCESS')

web_cfg = ReadConfig(cm.web_ini_file)

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


### 5.2 Чтение конфигурационных файлов read_cfg.py

Конфигурационные файлы используются в формате ini. Предоставляется метод для чтения ini-файлов. В директории `common` создайте файл `read_cfg.py`. Код представлен ниже:

```python
"""
@File    :read_cfg.py
@Author  : TJC
@Date    : 2023/7/10
@Desc    :Обертка для чтения конфигурационных файлов ini
"""

import configparser
from config.config import cm


# Определите фактические значения
HOST = 'host'
DRIVER = 'driver'

class ReadConfig:
    """Класс для работы с ini-конфигурационным файлом"""

    def __init__(self, ini_file):
        self.file = ini_file
        self.config = configparser.RawConfigParser()  # При наличии символов % используйте Raw для чтения, чтобы не анализировать значения в конфигурационном файле
        self.config.read(self.file, encoding='utf-8')

    def _get(self, section, option):
        """Получение значения из конфигурационного файла"""
        return self.config.get(section, option)

    def _set(self, section, option, value):
        """Обновление значения в конфигурационном файле"""
        self.config.set(section, option, value)
        with open(cm.ini_file, 'w') as f:
            self.config.write(f)

    @property
    def web_url(self):
        return self._get(HOST, 'HOST')

    @property
    def web_driver_process(self):
        return self._get(DRIVER, 'DRIVER_PROCESS')

web_cfg = ReadConfig(cm.web_ini_file)

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

```### 6.1. Структура```    базовый класс (base): общие методы для страниц page
    страница (page): одна страница упакована в объект
    элемент (element): элементы страницы, в данном проекте используются .py файлы для хранения элементов страницы, что облегчает написание кода
    тест-кейс (testcase): хранение тестовых сценариев

### 6.2 Базовый класс page_base

В папке page_base создайте файл page_base.py со следующим кодом:

```python
"""
@File    :page_base.py
@Author  :TJC
@Date    :2023-07-21
@Desc    :Базовый класс: хранение всех общих методов для страниц
"""
from selenium.webdriver.support.select import Select
from selenium.webdriver.support.wait import WebDriverWait
from utils.logger import log
from utils.timer import sleep
from selenium.webdriver.common.action_chains import ActionChains


class Base:
    def __init__(self, driver):
        self.driver = driver

    def base_find(self, loc, timeout=30, poll=0.5):
        """
        Поиск элемента
        timeout: время ожидания, обычно 30 секунд, если элемент не найден за это время, генерируется ошибка
        poll: интервал проверки, по умолчанию 0.5 секунды, если есть мигающее уведомление, можно установить значение 0.1 секунды
        """
        return WebDriverWait(self.driver, timeout, poll).until(lambda x: x.find_element(*loc))

    def base_input(self, loc, text):
        """Ввод текста"""
        el = self.base_find(loc)
        el.clear()
        if text is not None:
            el.send_keys(text)
        log.info(f"{el} ввод текста: {text}")

    def base_click(self, loc):
        """Клик"""
        self.base_find(loc).click()
        sleep()
        log.info(f'Клик по кнопке: {loc}')

    def base_get_text(self, loc):
        """Получение текущего текста элемента"""
        _text = self.base_find(loc).text
        log.info(f"Получение текста: {_text}")
        return _text
``````python
def base_get_title(self):
    """Получение текущего заголовка страницы"""
    title = self.driver.title
    log.info(f"Текущий заголовок страницы: {title}")
    return title

def base_alert_confirm(self):
    """Автоматическое подтверждение алерта для продолжения тестирования"""
    self.driver.switch_to().alert().accept()

def base_is_dis(self, loc):
    """Проверка видимости элемента"""
    state = self.base_find(loc).is_displayed()
    log.info(f"Получение состояния видимости элемента: {state}")
    return state
## Основные методы

```python
def base_keep_press(self, loc, time):
    """Задержка нажатия"""
    ActionChains(self.driver).click_and_hold(self.base_find(loc)).perform()
    log.info(f"Задержка нажатия: {loc}")
    sleep(time)
    ActionChains(self.driver).release(self.base_find(loc)).perform()
    log.info(f"Отпускание: {loc}")

def base_select(self, loc, text):
    """
    Выбор элемента из выпадающего списка
    :param loc: элемент селектора, родительский элемент, а не option
    :param text: выбор по видимому тексту
    """
    Select(self.base_find(loc)).select_by_visible_text(text)
    log.info(f"Выбор элемента из выпадающего списка {loc}: {text}")

def base_tick_checkbox(self, loc, num):
    """Галочка в чекбоксе"""
    checkbox_list = self.base_find(loc)
    action = ActionChains(self.driver)
    for i in range(0, num):
        action.move_to_element(checkbox_list[i])
        action.click(checkbox_list[i]).perform()
        sleep()

def base_invisible_element(self, loc, num, stop):
    """Выполнение действий на динамически изменяющихся элементах"""
    msg = self.base_find(loc)
    for i in range(0, num):
        action = ActionChains(self.driver)
        action.move_to_element(msg[i])
        action.click(msg[i])
        action.perform()
        sleep(stop)
### 6.3 Элементы страницы page_element

В директории `page_element` создайте файл `el_xxx.py`, где каждый `.py` файл будет содержать информацию о элементах конкретной страницы.

Например, создайте файл `el_login.py`, который будет содержать информацию о элементах страницы входа:

```python
"""
@File    :el_login.py
@Author  :TJC
@Date    :2023-07-21
@Desc    :Элементы страницы входа
"""

from selenium.webdriver.common.by import By

by_id = By.ID
by_name = By.NAME
by_class_name = By.CLASS_NAME
by_tag_name = By.TAG_NAME
by_link_text = By.LINK_TEXT
by_css_selector = By.CSS_SELECTOR
by_xpath = By.XPATH

# Элементы страницы входа
login_window = by_xpath, '//*[@id="login"]/div/div[1]/div'
login_username = by_xpath, '//*[@placeholder="Имя пользователя"]'
login_pwd = by_id, 'password'
login_err_info = by_css_selector, '.el-message__content'
login_btn = by_xpath, '//*[text()="Войти"]'
logout_btn = by_xpath, '//*[@id="login"]/div/div[2]/div/form/div[4]/div/span[3]'
logout_cancel = by_xpath, '/html/body/div[2]/div/div[3]/button[1]'
logout_confirm = by_xpath, '/html/body/div[2]/div/div[3]/button[2]'
```

### 6.4 Объекты страниц

В директории `page_object` создайте файл `page_xxx.py`. Каждый `.py` файл хранит информацию о одной странице.
Например, создайте файл `page_login.py`, который будет содержать информацию о странице входа, как показано ниже:

```python
"""
@File    :page_login.py
@Author  :TJC
@Date    :2cq3-07-21
@Desc    :Пакет для страницы входа
"""

from allure import step

from utils.timer import sleep
from page_base.page_base import Base
from page_element import el_login


class PageLogin(Base):
    """Страница входа"""
```
``````markdown
## 6.5 Структура тестовых случаев в директории testcases

В директории `testcases` создайте пакеты Python для хранения тестовых случаев различных модулей. Например, создайте папки `web` и `app`, чтобы хранить тестовые случаи для веб-приложений и мобильных приложений соответственно.

В папке `web` создайте файлы `test_login`, `test_group_call` и другие.
```

```python
    def page_input_username(self, username):
        """Ввод имени пользователя"""
        self.base_input(el_login.login_username, username)
        sleep()

    def page_input_passwd(self, password):
        """Ввод пароля"""
        self.base_input(el_login.login_pwd, password)
        sleep()

    def page_input_verify_code(self, verify_code):
        """Ввод кода подтверждения"""
        pass

    def page_click_login_btn(self):
        """Нажатие кнопки входа"""
        self.base_click(el_login.login_btn)
        sleep()

    def page_get_error_info(self):
        """Получение информации об ошибке"""
        return self.base_get_text(el_login.login_err_info)

    def page_click_err_btn_ok(self):
        pass

    def page_click_quit_btn(self):
        """Нажатие кнопки выхода"""
        self.base_click(el_login.login_btn)
        sleep()

    def page_click_quit_cancel(self):
        """Отмена выхода"""
        self.base_click(el_login.logout_cancel)
        sleep()

    def page_click_quit_confirm(self):
        """Подтверждение выхода"""
        self.base_click(el_login.logout_confirm)
        sleep()

    def page_login(self, username, password):
        sleep(2)
        with allure.step(f"Ввод имени пользователя: {username}"):
            self.page_input_username(username)
        with allure.step(f"Ввод пароля: {password}"):
            self.page_input_passwd(password)
        sleep(5)
        with allure.step("Нажатие кнопки входа"):
            self.page_click_login_btn()
```Если разделение директорий не требуется, его можно не выполнять. Разделение директорий помогает организовать тестовые случаи и упрощает автоматическое выполнение файла `conftest.py`.

### 6.6. conftest.py

В директории `testcases` создайте файл `conftest.py`, в котором будут содержаться все необходимые для тестовых сценариев фикстуры. Описание файла `conftest.py` можно найти в статье «Введение в pytest»: https://blog.csdn.net/weixin_59868574/article/details/135500904.
Файл `conftest.py` содержит веб-драйвер, получение скриншотов для отчетов Allure при неудачном выполнении тестов, вывод названия тестового случая. Код представлен ниже:

```markdown
"""
@File    :testcases\conftest.py
@Author  :TJC
@Date    :2023-07-21
@Desc    :предварительная настройка для веб-тестовых сценариев
"""

import pytest
import psutil
import allure
from selenium import webdriver
from utils.timer import dt_strftime
from utils.logger import log
from common.read_cfg import web_cfg
from config.config import cm

web_driver = None


@allure.title("Веб-драйвер")
@pytest.fixture(scope='session', autouse=True)
def drivers(request):

    global web_driver

    if web_driver is None:
        option = webdriver.ChromeOptions()
        option.binary_location = r'D:\HBFEC\AcroDCYJ\现场应急调度平台.exe'  # Измените путь в соответствии с вашими требованиями
        web_driver = webdriver.Chrome(executable_path=cm.web_driver, options=option)

    @allure.step("Закрытие веб-драйвера")
    def fn():
        web_driver.quit()
```    @pytest.fixture(scope='function', autouse=True)
    def close_quit(self):
        process_name = web_cfg.web_driver_process
        # Поиск всех процессов ChromeDriver
        process_list = [process for process in psutil.process_iter() if process.name() == process_name]
        if len(process_list) > 0:
            # Если есть несколько запущенных процессов ChromeDriver, завершаем все
            for process in process_list:
                process.kill()
            log.info('Обнаружены процессы ChromeDriver, все они были завершены')
        else:
            log.info('Процессов ChromeDriver нет')```markdown
    request.addfinalizer(fn)
    with allure.step("Возврат веб-драйвера"):
        return web_driver


@pytest.hookimpl(tryfirst=True, hookwrapper=True)
def pytest_runtest_makereport(item, call):
    # Пост-обработка, получение результатов теста
    outcome = yield
    result = outcome.get_result()

    if result.when == 'call' and result.failed:
        # При неудаче теста производится скриншот, добавляем его в отчет Allure
        screenshot = web_driver.get_screenshot_as_png()
        # Название файла для скриншота
        filename = '_'.join([result.nodeid.replace('testcases/', '').replace('::', '_'), datetime.strftime(datetime.now(), '%Y%m%d_%H%M%S')])
        allure.attach(screenshot, name=filename, attachment_type=allure.attachment_type.PNG)


@pytest.hookimpl(tryfirst=True, hookwrapper=True)
def pytest_runtest_call(item):
    # Запись запущенного тест-кейса
    case_name = item.nodeid.replace('testcases/', '').replace('::', '_')
    log.info('case:%s', case_name)
    yield
```

### 6.7 Тестовый сценарий test_xxx.py

Создайте файл `test_xxx.py` в директории `testcases` или её поддиректории, чтобы написать тестовые сценарии, используя методы, определённые в страницах.

Пример:
Создайте файл `test_login.py` в директории `testcases`, содержащий следующий код:

```python
"""
@File    :test_login.py
@Author  :TJC
@Date    :2023-07-21
@Desc    :Тестовые сценарии для модуля входа
"""
``````markdown
import allure
import pytest
from common.read_yaml import login_para
from utils.logger import log
from common.common import Common
from page_object.login import PageLogin


@allure.feature("Функциональность входа")
class TestLogin(Common):
    """Тестирование входа"""

    @allure.story("Неудачный вход")
    @allure.title("Проверка входа -{login_case}")
    @allure.description("Тестирование различных ситуаций при входе")
    @allure.severity("minor")  
    @pytest.mark.parametrize("login_case", login_para)
    def test_login(self, drivers, login_case):
        username = login_case['username']
        password = login_case['password']
        expect_text = login_case['expect']
        log.info(f"Имя пользователя: {username}")
        log.info(f"Пароль: {password}")
        log.info(f"Ожидаемый текст: {expect_text}")

        PageLogin(drivers).page_login(username, password)
        with allure.step("Получение сообщения об ошибке"):
            error_info = PageLogin(drivers).page_get_error_info()
        with allure.step("Проведение проверки"):
            assert expect_text in error_info


    @allure.story("Успешный вход")
    @allure.title("Успешный вход")
    @allure.description("Ввод корректного и легального имени пользователя и пароля для успешного входа")
    @allure.severity("критический")  
    def test_login_suc(self, drivers):
        """Необходимо добавить шаги тестирования"""
        log.info(drivers.title)
        log.info("Ввод корректного имени пользователя и пароля для успешного входа")


### 7 (1), Данные, используемые для привязки, JSON

Формат данных для тестирования не имеет единого стандарта и может использовать формат JSON.
Возвращаемое значение JSON может быть однослойным или многослойным параметром. В этом разделе рассматривается многослойное параметрирование.
```### 7.1 Тестовые данные test_data.json

Создайте файл тестовых данных test_data.json в директории testdata. Пример представлен ниже:

```json
{
  "login": [
    {
      "desc": "Неудачный вход (пользователя нет)",
      "username": "700001",
      "password": "700001@mcx",
      "expect": "Не удалось войти в систему"
    },
    {
      "desc": "Неудачный вход (пустой пароль)",
      "username": "700002",
      "password": "",
      "expect": "Неудача"
    },
    {
      "desc": "Неудачный вход",
      "username": "700003",
      "password": "700003",
      "expect": "Неудача"
    }
  ],
  "group_call": [
    {
      "desc": "Успешный групповой вызов",
      "group_id": "80001",
      "time": "10",
      "expect": "Вызов успешен"
    },
    {
      "desc": "Успешный групповой вызов",
      "group_id": "80002",
      "time": "10",
      "expect": "Вызов успешен"
    },
    {
      "desc": "Успешный групповой вызов",
      "group_id": "80003",
      "time": "10",
      "expect": "Вызов успешен"
    }
  ]
}
```

### 7.2 Чтение файла JSON read_json.py (список кортежей)

Создайте файл read_json.py в директории common для чтения тестовых данных, которые будут использоваться в тестовых случаях. Код представлен ниже:

```python
"""
@File    :read_json.py
@Author  :TJC
@Date    :2023-07-21
@Desc    :Чтение тестовых данных в формате JSON
"""

import json

from config.config import cm


class ReadJson:

    def __init__(self, json_file):
        self.json_file = json_file
``````python
    def read_json(self, key):
        """Возвращает список кортежей"""
        arr = []  
        with open(self.json_file, "r", encoding="utf-8") as f:
            datas = json.load(f)
            value = datas.get(key)
            for data in value:
                arr.append(tuple(data.values())[1:])
            return arr
```

```markdown
json_para = ReadJson(cm.test_json_file)
# Чтение соответствующих модулей из JSON файла, каждый модуль — один экземпляр
login_para = json_para.read_json('login')
group_call_para = json_para.read_json('group_call')
```# Получаем список кортежей в следующем формате:
# [('700001', '700001', 'Успешный вход'), ('700002', '', 'Неудачный вход, пароль не может быть пустым'), ('700003', '700003', 'Успешный вход')]
```

### 7.3. Тестовый пример test_xxx.py (множественные параметры)

Вышеупомянутый метод чтения данных из JSON возвращает список кортежей, используемый для множественного параметризованного тестирования.
Пример:
Создайте файл `test_login.py` в директории `testcases`, содержащий следующий код:

```python
"""
@File    :test_login.py
@Author  :TJC
@Date    :2023-07-21
@Desc    :Тестовые случаи для модуля входа
"""

import allure
import pytest
from common.read_json import login_para
from utils.logger import log
from common.common import Common
from utils.timer import sleep
from page_object.page_login import PageLogin


@allure.feature("Функционал входа")
class TestLogin(Common):
    """Тестирование входа"""

    @allure.story("Неудачный вход")
    @allure.title("Обнаружение неудачного входа - {username} - {password} - {expect_text}")
    @allure.description("Тестирование различных ситуаций при неудачном входе")
    @allure.severity("minor")
    @pytest.mark.parametrize("username, password, expect_text", login_para)
    # Множественное параметризованное тестирование, параметры можно использовать напрямую,
    # но все параметры должны быть указаны в parametrize и тестовой функции, а также должны совпадать по порядку
    def test_login(self, drivers, username, password, expect_text):
        PageLogin(drivers).page_login(username, password)
        with allure.step("Получение сообщения об ошибке"):
            error_info = PageLogin(drivers).page_get_error_info()
        with allure.step("Начало проверки"):
            assert expect_text in error_info
```## 7.2 Данные для тестирования yaml

Формат тестовых данных не стандартизирован, можно использовать YAML.
В этом разделе рассматриваются однопараметрическое и многопараметрическое параметризованное тестирование.

### 7.1 Тестовые данные test_data.yaml

Создайте файл с тестовыми данными `test_data.yaml` в директории `testdata`, как показано ниже:

```yaml
login:
  - case1: Успешный вход 1
    username: 70001
    password: 7001@mcx
    expect: Неверный пароль
  - case2: Успешный вход 2
    username: 70002
    password: 
    expect: Пароль не может быть пустым
  - case3: Успешный вход 3
    username: 70003
    password: 70003
    expect: Успешный вход
```

### 7.2 Чтение файла YAML read_yaml.py — одиночный параметр - список словарей

В директории common создайте новый файл read_yaml.py для чтения тестовых данных, используемых в тестовых сценариях. Код представлен ниже:

```python
"""
@File    :read_yaml.py
@Author  :TJC
@Date    :2023-07-21
@Desc    :Чтение тестовых данных в формате YAML, возвращаемое значение — список словарей
"""

import yaml

from config.config import cm


class ReadYaml:

    def __init__(self, yaml_file):
        self.yaml_file = yaml_file

    def read_yaml(self, key):
        with open(self.yaml_file, 'r', encoding='utf-8') as f:
            datas = yaml.safe_load(f)  # Предотвратите вывод предупреждений, добавив Loader=yaml.FullLoader
            value = datas[key]
            return value

yaml_para = ReadYaml(cm.test_yaml_file)
login_para = yaml_para.read_yaml('login')
"""
Возвращает список словарей
[
    {'case1': 'Успешный вход 1', 'username': 70001, 'password': '7001@mcx', 'expect': 'Неверный пароль'},
    {'case2': 'Успешный вход 2', 'username': 70002, 'password': None, 'expect': 'Пароль не может быть пустым'},
    {'case3': 'Успешный вход 3', 'username': 70003, 'password': 70003, 'expect': 'Успешный вход'}
]
"""
```### 7.3 Тестовый сценарий test_xxx.py — одиночный параметр параметризации

Вышеупомянутый метод чтения данных из YAML возвращает список словарей. Используется одиночный параметр для управления.
Пример:
Создайте файл test_login.py в директории testcases, код представлен ниже:

```python
"""
@File    :test_login.py
@Author  :TJC
@Date    :2023-07-21
@Desc    :Тестовые сценарии для модуля входа
"""

import allure
import pytest
from common.read_yaml import login_para
from utils.logger import log
from common.common import Common
from utils.timer import sleep
from page_object.page_login import PageLogin


@allure.feature("Функция входа")
class TestLogin(Common):
    """Тест входа"""

    @allure.story("Неудачный вход")
    @allure.title("Проверка входа -{login_case}")
    @allure.description("Тестирование различных исключений при входе")
    @allure.severity("minor")
    @pytest.mark.parametrize("login_case", login_para)
    def test_login(self, drivers, login_case):
        username = login_case['username']
        password = login_case['password']
        expect_text = login_case['expect']
        log.info(f"Имя пользователя: {username}")
        log.info(f"Пароль: {password}")
        log.info(f"Ожидаемый текст: {expect_text}")
```
```python
        PageLogin(drivers).page_login(username, password)
        with allure.step("Получение сообщения об ошибке"):
            error_info = PageLogin(drivers).page_get_error_info()
        with allure.step("Начало проверки"):
            assert expect_text in error_info
```

###  Yöntem YAML dosyasından veri okuma read_yaml.py (çoklu parametreler - liste tuple'ları)

Yukarıda belirtilen dosya `read_yaml.py` oluşturun ve bu dosya JSON ile benzer şekilde liste tuple'ları döndürmelidir. Kod aşağıdaki gibidir:```python
"""
@File    :read_yaml.py
@Author  :TJC
@Date    :2023-07-21
@Desc    :Чтение данных тестов в формате YAML
"""

import yaml

from config.config import cm


class ReadYaml:

    def __init__(self, yaml_file):
        self.yaml_file = yaml_file

    def read_yaml(self, key):
        arr = []
        with open(self.yaml_file, 'r', encoding='utf-8') as f:
            datas = yaml.safe_load(f)  # Избегаем вывода предупреждений, используя yaml.safe_load
            value = datas[key]
            for data in value:
                arr.append(tuple(data.values())[1:])
            return arr

yaml_para = ReadYaml(cm.test_yaml_file)
login_para = yaml_para.read_yaml('login')
```

### 7.5 Тестовый случай test_xxx.py (множественные параметры - параметризация)

Аналогично разделу 7.3, вам нужно будет изменить импорт:

```python
from common.read_yaml import login_para
```

### 7.6 Сравнение одиночных и множественных параметров

**1. Декоратор @pytest.mark.parametrize**

Одиночный параметр, использует один параметр и передает одну переменную.

```python
@pytest.mark.parametrize("login_case", login_para)
```

Множественные параметры, используют несколько параметров и должны гарантировать последовательность параметров, соответствующую порядку значений в переменной `login_para`.

```python
@pytest.mark.parametrize("username, password, expect_text", login_para)
```

**2. Функция def test_login параметры**

Одиночный параметр, использует параметризацию parametrize с одним параметром.

```python
def test_login(self, drivers, login_case):
```

Множественные параметры, используют параметризацию parametrize с несколькими параметрами.

```python
def test_login(self, drivers, username, password, expect_text):
```**3. Декоратор allure @allure.title**

Для отображения параметризованных данных в Allure, параметризованные данные должны быть помещены в заголовок.

Одиночный параметр использует параметризацию parametrize с одним параметром.

```python
@allure.title("Проверка входа - {login_case}")
```

Множественные параметры используют параметризацию parametrize с несколькими параметрами.

```python
@allure.title("Проверка входа - {username}, {password}, {expect_text}")
```

```python
@allure.title("Логин异常检测-{username}-{password}-{expect_text}")
```

**4. Параметры использования**

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

```python
username = login_case['username']
password = login_case['password']
expect_text = login_case['expect']
log.info(f"username:{username}")
log.info(f"password: {password}")
log.info(f"expect text: {expect_text}")
```

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

**5. Логирование**

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

```python
case: test_login.py_TestLogin_test_login[login_case0]
username:70001
password: 7001@mcx
expect text: неудача
case: test_login.py_TestLogin_test_login[login_case1]
username:70002
password: None
expect text: пароль
```

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

```python
случай: test_login.py_TestLogin_test_login[700001-700001@mcx-неудачный вход и регистрация]
случай: test_login.py_TestLogin_test_login[700002-неудача]
случай: test_login.py_TestLogin_test_login[700003-700003-неудача]
```

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

**6. Отображение Allure**
 Однопараметрические, нерегулярные заголовки тестовых случаев, трудночитаемые параметры, как показано ниже:

Скриншоты неудачных тестовых случаев не показывают детали параметров, как показано ниже:

Многопараметрические, регулярные заголовки тестовых случаев, четко читаемые параметры, как показано ниже:

Скриншоты неудачных тестовых случаев показывают тестовые параметры, как показано ниже:

На основе вышеуказанного сравнения выберите подходящий формат тестовых данных и метод параметризации для реализации данных-драйвинга.

## 8. Allure отчет

### 8.1. Содержание отчета allure_des

В папке allure_des создайте файл allure_des.py, используемый для создания информации об окружении, генераторе и статистике отчета, код следующий:

```
"""
@File    :allure_des.py
@Author  :TJC
@Date    :2023-07-21
@Desc    :информация описания отчета Allure, каждый запуск создает заново
"""
``````python
import json
import os
import platform
import pytest
from config.config import cm
```

```markdown
def set_report_env_on_results():
    """
    Создает файл с информацией о среде выполнения: environment.properties (обратите внимание: в файле не должно быть кириллицы, чтобы избежать проблем с отображением).
    """
    # Ваши данные о среде выполнения, заполните их согласно вашему проекту
    allure_env = {
        'OperatingEnvironment': 'mcx DC',
        'PythonVersion': platform.python_version(),
        'PytestVersion': pytest.__version__,
        'Platform': platform.platform(),
        'selenium': '3.141.0',
        'Browser': 'Chrome',
        'BrowserVersion': '59.0.3071.115',
        'DriverVersion': '2.32 MCX-driver'
    }
```    
    allure_env_file = os.path.join(cm.dir_report_json, 'environment.properties')
    with open(allure_env_file, 'w', encoding='utf-8') as f:
        for _k, _v in allure_env.items():
            f.write(f'{_k}={_v}\n')
```def set_report_executer_on_results(): 
    """
    Создает файл executor.json с информацией о выполнителе в директории json-отчета.
    """
    # Информация о выполнителе
    allure_executor = {
        "name": "Иванов Иван",
        "type": "jenkins",
        "url": "http://helloqa.com",  # адрес отчета Allure
        "buildOrder": 3,
        "buildName": "allure-report_deploy#1",
        "buildUrl": "http://helloqa.com#1",
        "reportUrl": "http://helloqa.com#1/AllureReport",
        "reportName": "Отчет Allure Иванова Ивана"
    }
    allure_env_file = os.path.join(cm.dir_report_json, 'executor.json')
    with open(allure_env_file, 'w', encoding='utf-8') as f:
        f.write(str(json.dumps(allure_executor, ensure_ascii=False, indent=4)))


def set_report_categories_on_results():
    """
    Создает файл categories.json с информацией о категориях тестов в директории json-отчета.
    """
    # Информация о категориях тестов
    allure_categories = [
          {
            "name": "Пропущенные тесты",
            "matchedStatuses": ["skipped"]
          },
          {
            "name": "Проблемы инфраструктуры",
            "matchedStatuses": ["broken", "failed"],
            "messageRegex": ".*bye-bye.*"
          },
          {
            "name": "Устаревшие тесты",
            "matchedStatuses": ["broken"],
            "traceRegex": ".*FileNotFoundException.*"
          },
          {
            "name": "Ошибки продукта",
            "matchedStatuses": ["failed"]
          },
          {
            "name": "Ошибки тестирования",
            "matchedStatuses": ["broken"]
          }
        ]

    allure_cat_file = os.path.join(cm.dir_report_json, 'categories.json')
    with open(allure_cat_file, 'w', encoding='utf-8') as f:
        f.write(str(json.dumps(allure_categories, ensure_ascii=False, indent=4)))## 9. Запуск pytest

### 9.1. Файл pytest.ini

В корневой директории проекта создайте файл pytest.ini для хранения параметров выполнения тестов. Содержимое файла:

```ini
[pytest]
addopts = --tb=short -v
```

### 9.2. Запуск тестов

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

```bash
pytest
```

Это запустит все тесты, найденные в текущем каталоге и всех его подкаталогах.```markdown
# pytest.ini
[pytest]
disable_test_id_escaping_and_forfeit_all_rights_to_community_support = True
addopts = -vs --clean-alluredir
testpaths = ./testcases
python_files = test*.py
python_classes = Test*
python_functions = test_*
```

### 9.2. Файл runtest.py

В корневой директории проекта создайте файл runtest.py, который будет служить точкой входа для запуска тестов. Код файла:

```python
"""
@File    : runtest.py
@Author  : TJC
@Date    : 2023-07-21
@Desc    : Точка входа для запуска тестовых случаев
"""

import os
import pytest

from allure_des.allure_des import set_report_env_on_results, \
    set_report_executer_on_results, \
    set_report_categories_on_results
from config.config import cm
from utils.timer import sleep


def run():
    pytest.main(['--allure-stories=Успешный вход,Неудачный вход', '--alluredir=%s' % cm.dir_report_json])
    # Создание файла categories.json в директории json
    set_report_categories_on_results()
    # Создание файла environment.properties в директории json
    set_report_env_on_results()
    # Создание файла executor.json в директории json
    set_report_executer_on_results()
    sleep(3)
    os.system("allure generate %s -o %s --clean" % (cm.dir_report_json, cm.dir_report_html))


if __name__ == '__main__':
    run()
```

---------------------- Конец. Удачи!
```

Комментарии ( 0 )

Вы можете оставить комментарий после Вход в систему

Введение

Python+Selenium+Pytest+PO+Allure+DDT+Log для реализации автоматизированного тестирования веб-интерфейса Развернуть Свернуть
Отмена

Обновления

Пока нет обновлений

Участники

все

Язык

Недавние действия

Загрузить больше
Больше нет результатов для загрузки
1
https://api.gitlife.ru/oschina-mirror/tianjianchao-TestCodeUI.git
git@api.gitlife.ru:oschina-mirror/tianjianchao-TestCodeUI.git
oschina-mirror
tianjianchao-TestCodeUI
tianjianchao-TestCodeUI
master