Initial commit

This commit is contained in:
KamilM1205 2026-01-19 23:32:11 +04:00
commit 9795660e1f
43 changed files with 2757 additions and 0 deletions

0
tests/api/__init__.py Normal file
View file

64
tests/api/conftest.py Normal file
View file

@ -0,0 +1,64 @@
import logging
import allure
import pytest
from config.session_config import session_config
from tests.api.utils.api_client import APIClient
logger = logging.getLogger(__name__)
@pytest.fixture(scope="class")
def api_client(api_base_url):
"""Клиент для работы с API"""
return APIClient(base_url=api_base_url)
@pytest.fixture(scope="class")
def admin_credentials():
"""Учетные данные администратора"""
return session_config.get_admin_credentials()
@pytest.fixture(scope="class")
def auth_admin(api_client: APIClient, admin_credentials):
with allure.step("Admin authentication fixture"):
logger.info("Authentificate admin")
success = api_client.login(
admin_credentials["username"],
admin_credentials["password"]
)
assert success is True
assert api_client.logged_in is True
logger.info("Admin authenticated")
yield api_client
api_client.logout()
@pytest.fixture(scope="class")
def auth_user(api_client: APIClient, auth_admin: APIClient, api_user_auth_data):
id = ''
with allure.step("User auth fixture"):
logger.info("Creating new user for auth")
resp = auth_admin.create_user(api_user_auth_data)
assert resp.status_code is 201
id = resp.json()['id']
logger.info(f"Auth as user: {api_user_auth_data['username']}")
resp = api_client.login(
api_user_auth_data['username'],
api_user_auth_data['password']
)
assert resp
assert api_client.logged_in
yield api_client
api_client.logout()
auth_admin.delete_user(id)

206
tests/api/test_posts.py Normal file
View file

@ -0,0 +1,206 @@
from typing import Any, Dict
import pytest
import allure
import logging
from tests.api.utils.api_client import APIClient
logger = logging.getLogger(__name__)
# Фикстура для получения списка постов из API
@pytest.fixture(scope="function")
def list_of_posts(api_client):
with allure.step("Get all posts"):
resp = api_client.get_all_posts()
assert resp.status_code == 200
return resp.json()
# Отдельные классы вариант
@allure.feature("Posts")
@allure.story("Guest permissions")
class TestGuestPosts:
"""Тестирование операций с постами под гостем (неавторизованный доступ)"""
@allure.title("Guest: Creating new posts - should fail")
def test_guest_posts_creating(self, api_client, api_post_data):
with allure.step("Logged as guest"):
logger.info("Guest trying to create posts")
for post in api_post_data:
resp = api_client.create_post(post)
assert resp.status_code == 401
@allure.title("Guest: Update posts - should fail")
def test_guest_posts_update(self, api_client, list_of_posts):
posts = list_of_posts
with allure.step("Guest trying to change posts data"):
for post in posts:
logger.info(f"Guest changing post: {post['title']}")
post["title"] = "Changed by guest"
post["description"] = "Changed by guest"
post["content"] = "Changed by guest"
resp = api_client.update_post(post["id"], post)
assert resp.status_code == 401
@allure.title("Guest: Get all posts - should succeed")
def test_guest_get_all_posts(self, api_client):
with allure.step("Guest getting all posts"):
logger.info("Guest getting all posts")
resp = api_client.get_all_posts()
assert resp.status_code == 200
@allure.title("Guest: Get post by ID - should succeed")
def test_guest_get_post_by_id(self, api_client, list_of_posts):
posts = list_of_posts
if posts:
post_id = posts[0]["id"]
with allure.step(f"Guest getting post with ID {post_id}"):
logger.info(f"Guest getting post: {post_id}")
resp = api_client.get_post(post_id)
assert resp.status_code == 200
@allure.title("Guest: Delete posts - should fail")
def test_guest_posts_deleting(self, api_client, list_of_posts):
posts = list_of_posts
with allure.step("Guest trying to delete posts"):
for post in posts:
logger.info(f"Guest deleting post: {post['title']}")
resp = api_client.delete_post(post["id"])
assert resp.status_code == 401
@allure.feature("Posts")
@allure.story("Admin permissions")
class TestAdminPosts:
"""Тестирование операций с постами под администратором"""
@allure.title("Admin: Creating new posts - should succeed")
def test_admin_posts_creating(self, auth_admin, api_post_data, api_user_data):
id = ''
with allure.step("Logged as admin"):
pass
with allure.step("Create new user"):
user = api_user_data[0]
resp = auth_admin.create_user(user)
assert resp.status_code == 201
id = resp.json()['id']
with allure.step("Create posts"):
logger.info("Admin creating posts")
for post in api_post_data:
post['userId'] = id
resp = auth_admin.create_post(post)
assert resp.status_code == 201
@allure.title("Admin: Update posts - should succeed")
def test_admin_posts_update(self, auth_admin, list_of_posts):
posts = list_of_posts
with allure.step("Admin changing posts data"):
for post in posts:
logger.info(f"Admin changing post: {post['title']}")
new_post: Dict[str, Any] = {}
new_post["title"] = "Changed by admin."
new_post["description"] = "Changed by admin. Test data."
new_post["content"] = "Changed by admin."
resp = auth_admin.update_post(post["id"], new_post)
assert resp.status_code == 200
@allure.title("Admin: Get all posts - should succeed")
def test_admin_get_all_posts(self, auth_admin):
with allure.step("Admin getting all posts"):
logger.info("Admin getting all posts")
resp = auth_admin.get_all_posts()
assert resp.status_code == 200
@allure.title("Admin: Get post by ID - should succeed")
def test_admin_get_post_by_id(self, auth_admin, list_of_posts):
posts = list_of_posts
if posts:
post_id = posts[0]["id"]
with allure.step(f"Admin getting post with ID {post_id}"):
logger.info(f"Admin getting post: {post_id}")
resp = auth_admin.get_post(post_id)
assert resp.status_code == 200
@allure.title("Admin: Delete posts - should succeed")
def test_admin_posts_deleting(self, auth_admin, list_of_posts):
posts = list_of_posts
id = posts[0]['userId']
with allure.step("Admin deleting posts"):
for post in posts:
logger.info(f"Admin deleting post: {post['title']}")
resp = auth_admin.delete_post(post["id"])
assert resp.status_code == 200
with allure.step("Delete user"):
resp = auth_admin.delete_user(id)
assert resp.status_code == 200
@allure.feature("Posts")
@allure.story("Regular user permissions")
class TestRegularUserPosts:
"""Тестирование операций с постами под обычным пользователем"""
@allure.title("User: Creating new posts - should succeed")
def test_user_posts_creating(self, auth_user: APIClient, api_post_data):
id = auth_user.get_all_users().json()[0]['id']
with allure.step("Logged as user"):
pass
with allure.step("Create new posts"):
logger.info("User creating posts")
for post in api_post_data:
post['userId'] = id
resp = auth_user.create_post(post)
assert resp.status_code == 201
@allure.title("User: Update posts - should succeed")
def test_user_posts_update(self, auth_user, list_of_posts):
posts = list_of_posts
with allure.step("User changing posts data"):
for post in posts:
logger.info(f"User changing post: {post['title']}")
post["title"] = "Changed by user"
post["description"] = "Changed by user"
post["content"] = "Changed by user"
resp = auth_user.update_post(post["id"], post)
assert resp.status_code == 200
@allure.title("User: Get all posts - should succeed")
def test_user_get_all_posts(self, auth_user):
with allure.step("User getting all posts"):
logger.info("User getting all posts")
resp = auth_user.get_all_posts()
assert resp.status_code == 200
@allure.title("User: Get post by ID - should succeed")
def test_user_get_post_by_id(self, auth_user, list_of_posts):
posts = list_of_posts
if posts:
post_id = posts[0]["id"]
with allure.step(f"User getting post with ID {post_id}"):
logger.info(f"User getting post: {post_id}")
resp = auth_user.get_post(post_id)
assert resp.status_code == 200
@allure.title("User: Delete posts - should succeed")
def test_user_posts_deleting(self, auth_user, list_of_posts):
posts = list_of_posts
with allure.step("User deleting posts"):
for post in posts:
logger.info(f"User deleting post: {post['title']}")
resp = auth_user.delete_post(post["id"])
assert resp.status_code == 200

133
tests/api/test_users.py Normal file
View file

@ -0,0 +1,133 @@
import pytest
import allure
import logging
from tests.api.utils.api_client import APIClient
logger = logging.getLogger(__name__)
# Фикстура для получения списка пользователей
@pytest.fixture(scope="class")
def list_of_users(api_client):
with allure.step("Get all users"):
resp = api_client.get_all_users()
assert resp.status_code == 200
return resp.json()
# Тест для гостя (неавторизованный пользователь)
@allure.story("Guest permissions")
class TestGuestUsers:
"""Тестирование операций с пользователями под гостем (неавторизованный доступ)"""
@allure.title("Guest: Creating new user - should fail")
def test_guest_users_creating(self, api_client: APIClient, api_user_data):
assert api_client.logged_in == False
with allure.step("Logged as guest - trying to create users"):
logger.info("Guest trying to create users")
for user in api_user_data:
resp = api_client.create_user(user)
assert resp.status_code == 401, \
f"Guest should not be able to create users. Got {resp.status_code}"
@allure.title("Guest: Update user - should fail")
def test_guest_users_update(self, api_client: APIClient, list_of_users):
users = list_of_users
with allure.step("Guest trying to change users data"):
for user in users:
logger.info(f"Guest changing user: {user['username']}")
user["motto"] = "Changed by guest"
resp = api_client.update_user(user["id"], user)
assert resp.status_code == 401, \
f"Guest should not be able to update users. Got {resp.status_code}"
@allure.title("Guest: Deleting users - should fail")
def test_guest_users_deleting(self, api_client: APIClient, list_of_users):
users = list_of_users
with allure.step("Guest trying to delete users"):
for user in users:
logger.info(f"Guest deleting user: {user['username']}")
resp = api_client.delete_user(user["id"])
assert resp.status_code == 401, \
f"Guest should not be able to delete users. Got {resp.status_code}"
# Тест для администратора
@allure.story("Admin permissions")
class TestAdminUsers:
"""Тестирование операций с пользователями под администратором"""
@allure.title("Admin: Creating new user - should succeed")
def test_admin_users_creating(self, auth_admin, api_user_data):
with allure.step("Logged as admin - creating users"):
logger.info("Admin creating users")
for user in api_user_data:
resp = auth_admin.create_user(user)
assert resp.status_code == 201, \
f"Admin should be able to create users. Got {resp.status_code}"
@allure.title("Admin: Update user - should succeed")
def test_admin_users_update(self, auth_admin, list_of_users):
users = list_of_users
with allure.step("Admin changing users data"):
for user in users:
logger.info(f"Admin changing user: {user['username']}")
user["motto"] = "Changed by admin"
user["password"] = "SomeRandomPass1205"
resp = auth_admin.update_user(user["id"], user)
assert resp.status_code == 200, \
f"Admin should be able to update users. Got {resp.status_code}"
@allure.title("Admin: Deleting users - should succeed")
def test_admin_users_deleting(self, auth_admin, list_of_users):
users = list_of_users
with allure.step("Admin deleting users"):
for user in users:
logger.info(f"Admin deleting user: {user['username']}")
resp = auth_admin.delete_user(user["id"])
assert resp.status_code == 200, \
f"Admin should be able to delete users. Got {resp.status_code}"
# Тест для обычного пользователя
@allure.story("Regular user permissions")
class TestRegularUserUsers:
"""Тестирование операций с пользователями под обычным пользователем"""
@allure.title("User: Creating new user - should succeed")
def test_user_users_creating(self, auth_user, api_user_data):
with allure.step("Logged as regular user - creating users"):
logger.info("Regular user creating users")
for user in api_user_data:
resp = auth_user.create_user(user)
assert resp.status_code == 201, \
f"Regular user should be able to create users. Got {resp.status_code}"
@allure.title("User: Update user - should succeed")
def test_user_users_update(self, auth_user, list_of_users):
users = list_of_users
with allure.step("Regular user changing users data"):
for user in users:
logger.info(f"Regular user changing: {user['username']}")
user["motto"] = "Changed by regular user"
user["password"] = "SomeRandomPass1205"
resp = auth_user.update_user(user["id"], user)
assert resp.status_code == 200, \
f"Regular user should be able to update users. Got {resp.status_code}"
@allure.title("User: Deleting users - should succeed")
def test_user_users_deleting(self, auth_user, list_of_users):
users = list_of_users
with allure.step("Regular user deleting users"):
for user in users:
logger.info(f"Regular user deleting: {user['username']}")
resp = auth_user.delete_user(user["id"])
assert resp.status_code == 200, \
f"Regular user should be able to delete users. Got {resp.status_code}"

View file

@ -0,0 +1,213 @@
import json
import requests
import allure
import logging
from typing import Optional, Dict, Any
from urllib.parse import urljoin
from config.api_config import api_config
from config.session_config import session_config
logger = logging.getLogger(__name__)
class APIClient:
def __init__(self, base_url: Optional[str] = None):
self.base_url = base_url or api_config.API_BASE_URL
self.session = requests.Session()
self.session.cookies.clear()
self.logged_in = False
logger.info("New API created.")
# Настройка сессии
self._configure_session()
def _configure_session(self):
"""Настройка HTTP сессии"""
# Установка заголовков по умолчанию
self.session.headers.update(api_config.DEFAULT_HEADERS)
self.session.headers.update(session_config.get_session_headers())
logger.debug(f"API Client configured for {self.base_url}")
def login(self, username: str, password: str) -> bool:
"""Логин пользователя (создание сессии)"""
login_data = {
"username": username,
"password": password
}
endpoint = api_config.get_endpoint('auth_login')
response = self.post(endpoint, json=login_data)
if response.status_code == 200:
self.logged_in = True
logger.info(f"Successfully logged in as {username}")
# Логирование cookies для отладки
if self.session.cookies:
cookies_info = dict(self.session.cookies)
logger.info(f"Session cookies: {cookies_info}")
# Проверяем наличие сессионного cookie
session_cookie = session_config.SESSION_COOKIE_NAME
if session_cookie in cookies_info:
logger.info(f"Session cookie '{session_cookie}' is set")
return True
else:
logger.error(f"Login failed for {username}: {response.status_code}")
logger.debug(f"Response: {response.text}")
return False
def logout(self) -> bool:
"""Выход из системы (закрытие сессии)"""
if not self.logged_in:
logger.warning("Attempted logout without being logged in")
return True
endpoint = api_config.get_endpoint('auth_logout')
response = self.get(endpoint)
if response.status_code == 200:
self.logged_in = False
# Очищаем cookies
self.session.cookies.clear()
logger.info("Successfully logged out")
return True
else:
logger.error(f"Logout failed: {response.status_code}")
return False
def ensure_logged_in(self, username: str, password: str):
"""Убедиться, что пользователь залогинен"""
if not self.logged_in:
return self.login(username, password)
return True
def _send_request(
self,
method: str,
endpoint: str,
**kwargs
) -> requests.Response:
"""Отправка HTTP запроса с поддержкой сессий"""
url = urljoin(self.base_url, endpoint)
with allure.step(f"{method.upper()} {endpoint}"):
# Добавляем логирование
self._log_request(method, url, kwargs)
try:
response = self.session.request(method, url, headers=api_config.DEFAULT_HEADERS, **kwargs)
except requests.exceptions.RequestException as e:
logger.error(f"Request failed: {e}")
raise
# Логирование ответа
self._log_response(response)
return response
def _log_request(self, method: str, url: str, kwargs: Dict[str, Any]):
"""Логирование деталей запроса"""
request_details = (
f"Request: {method} {url}\n"
f"Headers: {dict(self.session.headers)}\n"
f"Cookies: {dict(self.session.cookies)}\n"
f"Body: {kwargs.get('json', 'N/A')}"
)
allure.attach(
request_details,
name="Request Details",
attachment_type=allure.attachment_type.TEXT
)
logger.debug(f"{method} {url}")
def _log_response(self, response: requests.Response):
"""Логирование деталей ответа"""
response_details = (
f"Status Code: {response.status_code}\n"
f"Response Headers: {dict(response.headers)}\n"
f"Response Cookies: {dict(response.cookies)}\n"
f"Response Body: {response.text[:500]}..."
)
allure.attach(
response_details,
name="Response Details",
attachment_type=allure.attachment_type.TEXT
)
logger.debug(f"Response: {response.status_code}")
# Логирование ошибок
if response.status_code >= 400:
logger.warning(f"Request failed with status {response.status_code}")
logger.debug(f"Error response: {response.text}")
# Методы HTTP запросов
def get(self, endpoint: str, **kwargs) -> requests.Response:
return self._send_request("GET", endpoint, **kwargs)
def post(self, endpoint: str, **kwargs) -> requests.Response:
return self._send_request("POST", endpoint, **kwargs)
def put(self, endpoint: str, **kwargs) -> requests.Response:
return self._send_request("PUT", endpoint, **kwargs)
def delete(self, endpoint: str, **kwargs) -> requests.Response:
return self._send_request("DELETE", endpoint, **kwargs)
def patch(self, endpoint: str, **kwargs) -> requests.Response:
return self._send_request("PATCH", endpoint, **kwargs)
# Специальные методы для endpoints
def create_user(self, user_data: Dict[str, Any]) -> requests.Response:
endpoint = api_config.get_endpoint('users_create')
return self.post(endpoint, json=user_data)
def get_user(self, user_id: str) -> requests.Response:
endpoint = api_config.get_endpoint('users_get_by_id', id=user_id)
return self.get(endpoint)
def get_all_users(self) -> requests.Response:
endpoint = api_config.get_endpoint('users_get_all')
return self.get(endpoint)
def update_user(self, user_id: str, user_data: Dict[str, Any]) -> requests.Response:
endpoint = api_config.get_endpoint('users_update', id=user_id)
return self.put(endpoint, json=user_data)
def delete_user(self, user_id: str) -> requests.Response:
endpoint = api_config.get_endpoint('users_delete', id=user_id)
return self.delete(endpoint)
def create_post(self, post_data: Dict[str, Any]) -> requests.Response:
endpoint = api_config.get_endpoint('posts_create')
return self.post(endpoint, json=post_data)
def get_post(self, post_id: str) -> requests.Response:
endpoint = api_config.get_endpoint('posts_get_by_id', id=post_id)
return self.get(endpoint)
def get_all_posts(self) -> requests.Response:
endpoint = api_config.get_endpoint('posts_get_all')
return self.get(endpoint)
def update_post(self, post_id: str, post_data: Dict[str, Any]) -> requests.Response:
endpoint = api_config.get_endpoint('posts_update', id=post_id)
return self.put(endpoint, json=post_data)
def delete_post(self, post_id: str) -> requests.Response:
endpoint = api_config.get_endpoint("posts_delete", id=post_id)
return self.delete(endpoint)
def upload_image(self, file_path: str, field_name: str = "file") -> requests.Response:
endpoint = api_config.get_endpoint('images_upload')
with open(file_path, 'rb') as f:
files = {field_name: (file_path, f)}
return self.post(endpoint, files=files)

106
tests/conftest.py Normal file
View file

@ -0,0 +1,106 @@
import pytest
import logging
from config.settings import settings
from config.api_config import api_config
from config.ui_config import ui_config
from fixtures.data_fixtures import *
logger = logging.getLogger(__name__)
# Вывод информации о конфигурации
logger.info("=" * 50)
logger.info(f"Running tests in {settings.ENV} environment")
logger.info(f"API Base URL: {api_config.API_BASE_URL}")
logger.info(f"UI Base URL: {ui_config.UI_BASE_URL}")
logger.info("=" * 50)
def pytest_addoption(parser):
"""Добавление кастомных опций командной строки"""
parser.addoption(
"--env-file",
action="store",
default=None,
help="Path to custom .env file"
)
parser.addoption(
"--browser",
action="store",
default=ui_config.BROWSER_NAME,
help="Browser to use for UI tests"
)
parser.addoption(
"--headless",
action="store_true",
default=ui_config.BROWSER_HEADLESS,
help="Run browser in headless mode"
)
parser.addoption(
"--api-url",
action="store",
default=api_config.API_BASE_URL,
help="Override API base URL"
)
parser.addoption(
"--ui-url",
action="store",
default=ui_config.UI_BASE_URL,
help="Override UI base URL"
)
def pytest_configure(config):
"""Конфигурация pytest"""
# Переопределение настроек из командной строки
if config.getoption("--api-url"):
api_config.API_BASE_URL = config.getoption("--api-url")
if config.getoption("--ui-url"):
ui_config.UI_BASE_URL = config.getoption("--ui-url")
if config.getoption("--browser"):
ui_config.BROWSER_NAME = config.getoption("--browser")
if config.getoption("--headless") is not None:
ui_config.BROWSER_HEADLESS = config.getoption("--headless")
# Настройка маркеров
config.addinivalue_line(
"markers",
"smoke: Smoke tests - critical functionality"
)
config.addinivalue_line(
"markers",
"regression: Regression tests - full functionality"
)
config.addinivalue_line(
"markers",
"api: API tests"
)
config.addinivalue_line(
"markers",
"ui: UI tests"
)
config.addinivalue_line(
"markers",
"slow: Slow running tests"
)
@pytest.fixture(scope="session")
def env_config():
"""Конфигурация окружения"""
return {
"env": settings.ENV,
"debug": settings.DEBUG,
"api_url": api_config.API_BASE_URL,
"ui_url": ui_config.UI_BASE_URL,
"log_level": settings.LOG_LEVEL,
}
@pytest.fixture(scope="session")
def api_base_url():
"""Базовый URL API"""
return api_config.API_BASE_URL
@pytest.fixture(scope="session")
def ui_base_url():
"""Базовый URL UI"""
return ui_config.UI_BASE_URL

38
tests/data/api_posts.json Normal file
View file

@ -0,0 +1,38 @@
[
{
"category": "Web Development",
"content": "Разбираем основы React.js: компоненты, состояние и пропсы. Создадим простое приложение-список задач, чтобы понять, как работает виртуальный DOM и управление состоянием.",
"description": "Введение в React.js для начинающих",
"tags": ["React", "фронтенд", "JavaScript"],
"title": "Первый проект на React.js: список задач"
},
{
"category": "Cybersecurity",
"content": "Обзор основных методов защиты веб‑приложений: HTTPS, CSP, защита от SQLинъекций и XSS. Приведём примеры кода для безопасной обработки пользовательских данных.",
"description": "Основы безопасности веб‑приложений",
"tags": ["безопасность", "веб", "защита данных"],
"title": "Как защитить веб‑приложение: 5 ключевых методов"
},
{
"category": "Cloud Computing",
"content": "Сравниваем AWS, Google Cloud и Azure: цены, сервисы и сценарии использования. Разберём, как выбрать облачную платформу для стартапа и крупного бизнеса.",
"description": "Выбор облачного провайдера: сравнение",
"tags": ["облако", "AWS", "Google Cloud", "Azure"],
"title": "AWS vs Google Cloud vs Azure: что выбрать?"
},
{
"category": "DevOps",
"content": "Настройка CI/CD с GitHub Actions: автоматизируем тестирование и деплой. Покажем, как создать пайплайн для Node.jsприложения за 10 минут.",
"description": "CI/CD на практике с GitHub Actions",
"tags": ["DevOps", "CI/CD", "GitHub Actions"],
"title": "Автоматизация сборки и деплоя: GitHub Actions"
},
{
"category": "Mobile Development",
"content": "Создаём кросс‑платформенное приложение на Flutter: от установки SDK до первого экрана. Разберём архитектуру и преимущества Flutter перед Native разработкой.",
"description": "Начало работы с Flutter",
"tags": ["Flutter", "мобильная разработка", "кросс‑платформа"],
"title": "Flutter: пишем первое мобильное приложение"
}
]

View file

@ -0,0 +1,13 @@
{
"avatar": "🔍",
"description": "QAинженер с фокусом на автоматизированное тестирование и CI/CD.",
"joinDate": "2023-10-01T20:00:00Z",
"motto": "Тестирование — это поиск истины.",
"name": "Андрей Васильев",
"password": "TestPas1205",
"projects": ["Web App Testing", "API Automation"],
"role": "QA Engineer",
"skills": ["Selenium", "JUnit", "Postman", "CI/CD", "TestRail"],
"speciality": "Good user",
"username": "andrey_user"
}

132
tests/data/api_users.json Normal file
View file

@ -0,0 +1,132 @@
[
{
"avatar": "🧑",
"description": "Опытный разработчик FullStack с 8летним стажем. Специализируется на веб‑приложениях и микросервисах.",
"joinDate": "2023-05-12T12:00:00Z",
"motto": "Код — это поэзия логики.",
"name": "Алексей Петров",
"password": "TestPas1205",
"projects": ["Ecommerce Platform", "CRM System", "Task Manager"],
"role": "Senior Developer",
"skills": ["JavaScript", "Python", "Docker", "Kubernetes", "PostgreSQL"],
"speciality": "Web Development",
"username": "alex_dev"
},
{
"avatar": "👩",
"description": "UX/UIдизайнер с фокусом на пользовательский опыт и адаптивные интерфейсы.",
"joinDate": "2022-11-03T11:00:00Z",
"motto": "Дизайн — это не про красоту, а про удобство.",
"name": "Мария Иванова",
"password": "TestPas1205",
"projects": ["Mobile Banking App", "Elearning Platform"],
"role": "UI/UX Designer",
"skills": ["Figma", "Adobe XD", "User Research", "Prototyping"],
"speciality": "User Experience",
"username": "maria_design"
},
{
"avatar": "👨‍💻",
"description": "Системный администратор с экспертизой в облачных решениях и кибербезопасности.",
"joinDate": "2024-01-20T10:00:00Z",
"motto": "Безопасность — не опция, а необходимость.",
"name": "Дмитрий Соколов",
"password": "TestPas1205",
"projects": ["Cloud Migration", "Network Security Audit"],
"role": "SysAdmin",
"skills": ["AWS", "Azure", "Linux", "Firewall", "VPN"],
"speciality": "Cloud & Security",
"username": "dmitry_sys"
},
{
"avatar": "👩‍💼",
"description": "Менеджер проектов с опытом ведения кросс‑функциональных команд в ITсфере.",
"joinDate": "2023-08-15T14:12:00Z",
"motto": "Сроки — святое, но качество — важнее.",
"name": "Анна Кузнецова",
"password": "TestPas1205",
"projects": ["ERP Implementation", "Agile Transformation"],
"role": "Project Manager",
"skills": ["Agile", "Scrum", "Jira", "Stakeholder Management"],
"speciality": "Project Management",
"username": "anna_pm"
},
{
"avatar": "🤖",
"description": "Специалист по машинному обучению и анализу данных. Работает с NLP и компьютерным зрением.",
"joinDate": "2024-03-10T16:34:00Z",
"motto": "Данные — новая нефть, а ML — двигатель.",
"name": "Иван Морозов",
"password": "TestPas1205",
"projects": ["Chatbot Development", "Image Recognition System"],
"role": "ML Engineer",
"skills": ["Python", "TensorFlow", "PyTorch", "NLP", "Data Visualization"],
"speciality": "Machine Learning",
"username": "ivan_ml"
},
{
"avatar": "🎨",
"description": "Графический дизайнер, создаёт брендовые стили и маркетинговые материалы.",
"joinDate": "2023-06-22T12:44:00Z",
"motto": "Каждый пиксель имеет значение.",
"name": "Елена Волкова",
"password": "TestPas1205",
"projects": ["Brand Identity", "Social Media Graphics"],
"role": "Graphic Designer",
"skills": ["Photoshop", "Illustrator", "InDesign", "Branding"],
"speciality": "Graphic Design",
"username": "elena_art"
},
{
"avatar": "📊",
"description": "Аналитик данных с опытом работы в BIинструментах и SQL.",
"joinDate": "2022-09-05T14:32:00Z",
"motto": "Факты говорят громче слов.",
"name": "Сергей Новиков",
"password": "TestPas1205",
"projects": ["Sales Dashboard", "Customer Churn Analysis"],
"role": "Data Analyst",
"skills": ["SQL", "Power BI", "Excel", "Tableau", "Python"],
"speciality": "Data Analytics",
"username": "sergey_data"
},
{
"avatar": "📱",
"description": "Мобильный разработчик под iOS и Android. Специализируется на кросс‑платформенных решениях.",
"joinDate": "2024-02-18T18:23:00Z",
"motto": "Приложения — мосты между людьми и технологиями.",
"name": "Ольга Фёдорова",
"password": "TestPas1205",
"projects": ["Fitness App", "Delivery Service"],
"role": "Mobile Developer",
"skills": ["Swift", "Kotlin", "Flutter", "React Native"],
"speciality": "Mobile Development",
"username": "olga_mobile"
},
{
"avatar": "🔍",
"description": "QAинженер с фокусом на автоматизированное тестирование и CI/CD.",
"joinDate": "2023-10-01T20:00:00Z",
"motto": "Тестирование — это поиск истины.",
"name": "Андрей Васильев",
"password": "TestPas1205",
"projects": ["Web App Testing", "API Automation"],
"role": "QA Engineer",
"skills": ["Selenium", "JUnit", "Postman", "CI/CD", "TestRail"],
"speciality": "Quality Assurance",
"username": "andrey_qa"
},
{
"avatar": "📚",
"description": "Технический писатель, создаёт документацию и гайды для сложных систем.",
"joinDate": "2024-04-14T16:43:00Z",
"motto": "Ясность — ключ к пониманию.",
"name": "Наталья Смирнова",
"password": "TestPas1205",
"projects": ["API Documentation", "User Manual for ERP"],
"role": "Technical Writer",
"skills": ["Markdown", "Confluence", "Diagrams", "API Docs"],
"speciality": "Technical Documentation",
"username": "natalia_docs"
}
]

0
tests/fixtures/__init__.py vendored Normal file
View file

24
tests/fixtures/data_fixtures.py vendored Normal file
View file

@ -0,0 +1,24 @@
import json
from pathlib import Path
import pytest
def _load_data(filename: str):
current_file = Path(__file__).resolve()
data_path = current_file.parent.parent.joinpath("data")
data_file_path = data_path.joinpath(filename)
with open(data_file_path, 'r', encoding='utf-8') as f:
data = json.load(f)
return data
@pytest.fixture(scope="session")
def api_user_data():
return _load_data('api_users.json')
@pytest.fixture(scope="session")
def api_post_data():
return _load_data('api_posts.json')
@pytest.fixture(scope="session")
def api_user_auth_data():
return _load_data('api_user_auth.json')

15
tests/pytest.ini Normal file
View file

@ -0,0 +1,15 @@
[pytest]
testpaths = tests
pythonpath = ../
addopts =
-v
--strict-markers
--alluredir=reports/allure-results/
--clean-alluredir
-p no:warnings
markers =
smoke: Smoke tests
regression: Regression tests
api: API tests
ui: UI tests
slow: Slow running tests

0
tests/ui/__init__.py Normal file
View file

130
tests/ui/conftest.py Normal file
View file

@ -0,0 +1,130 @@
import logging
import allure
import pytest
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.support.ui import WebDriverWait
from datetime import datetime
from config.ui_config import UIConfig
from tests.api.utils.api_client import APIClient
from utils import waiters
from api.utils.api_client import APIClient
from api.conftest import *
from fixtures.data_fixtures import *
logger = logging.getLogger(__name__)
@pytest.fixture
def driver(request):
"""Фикстура для браузера"""
logger.info("=" * 50)
logger.info("Initializing webdriver")
headless = UIConfig.BROWSER_HEADLESS
browser_name = UIConfig.BROWSER_NAME
fullscreen = UIConfig.BROWSER_FULLSCREEN
logger.info(f"Headless: {headless}")
logger.info("Browser: {browser_name}")
logger.info("=" * 50)
if browser_name == "chrome":
options = Options()
if headless:
options.add_argument("--headless")
if fullscreen:
options.add_argument("--start-maximized")
options.add_argument("--no-sandbox")
options.add_argument("--disable-dev-shm-usage")
options.add_argument("--window-size=1920,1080")
driver = webdriver.Chrome(
options=options
)
else:
options = webdriver.FirefoxOptions()
if headless:
options.add_argument("--headless")
if fullscreen:
options.add_argument("--kiosk")
options.add_argument("--width=1920")
options.add_argument("--height=1080")
driver = webdriver.Firefox(
options=options
)
driver.implicitly_wait(10)
driver.maximize_window()
waiters.waiter = WebDriverWait(driver, 10)
def fin():
if request.node.rep_call.failed:
try:
# Делаем скриншот при падении теста
screenshot = driver.get_screenshot_as_png()
allure.attach(
screenshot,
name=f"screenshot_{datetime.now().strftime('%Y%m%d_%H%M%S')}",
attachment_type=allure.attachment_type.PNG
)
# Получаем текущий URL
current_url = driver.current_url
allure.attach(
current_url,
name="current_url",
attachment_type=allure.attachment_type.TEXT
)
# Получаем исходный код страницы
page_source = driver.page_source
allure.attach(
page_source,
name="page_source",
attachment_type=allure.attachment_type.HTML
)
except:
pass
driver.quit()
request.addfinalizer(fin)
yield driver
driver.quit()
@pytest.hookimpl(tryfirst=True, hookwrapper=True)
def pytest_runtest_makereport(item, call):
"""
Хук для получения результатов теста
"""
outcome = yield
rep = outcome.get_result()
setattr(item, "rep_" + rep.when, rep)
@pytest.fixture(scope="function")
def gen_data_fixture(auth_admin: APIClient, api_user_data, api_post_data):
with allure.step("Generating data"):
id = ''
user = api_user_data[0]
resp = auth_admin.create_user(user)
assert resp.status_code == 201
id = resp.json()['id']
post = api_post_data[0]
post['userId'] = id
auth_admin.create_post(post)
yield
with allure.step("Deleting generated data"):
posts = auth_admin.get_all_posts().json()
users = auth_admin.get_all_users().json()
for post in posts:
auth_admin.delete_post(post['id'])
for user in users:
auth_admin.delete_user(user['id'])

View file

View file

@ -0,0 +1,134 @@
import allure
from selenium.webdriver.chrome.webdriver import WebDriver
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys
from tests.ui.pages.base_page import BasePage
class AdminPage(BasePage):
# Локаторы
# Логин
LOGIN_BLOCK = (By.XPATH, "//div[contains(@id, 'operations-user-post_login')]//button[contains(@class, 'opblock-summary-control')]")
LOGIN_TRYITOUT_BTN = (By.XPATH, "//div[contains(@id, 'operations-user-post_login')]//button[contains(@class, 'try-out__btn')]")
LOGIN_TEXTAREA = (By.XPATH, "//div[contains(@id, 'operations-user-post_login')]//textarea[contains(@class, 'body-param__text')]")
LOGIN_EXECUTE_BTN = (By.XPATH, "//div[contains(@id, 'operations-user-post_login')]//button[contains(@class, 'execute')]")
LOGIN_STATUS_CODE = (By.XPATH, "//div[contains(@id, 'operations-user-post_login')]//table[contains(@class, 'live-responses-table')]//tbody//td[contains(@class, 'response-col_status')]")
# Пользователь
USER_BLOCK = (By.XPATH, "//div[contains(@id, 'operations-user-post_team_')]//button[contains(@class, 'opblock-summary-control')]")
USER_TRYITOUT_BTN = (By.XPATH, "//div[contains(@id, 'operations-user-post_team_')]//button[contains(@class, 'try-out__btn')]")
USER_TEXTAREA = (By.XPATH, "//div[contains(@id, 'operations-user-post_team_')]//textarea[contains(@class, 'body-param__text')]")
USER_EXECUTE_BTN = (By.XPATH, "//div[contains(@id, 'operations-user-post_team_')]//button[contains(@class, 'execute')]")
USER_RESPONSE = (By.XPATH, "//div[contains(@id, 'operations-user-post_team_')]//table[contains(@class, 'live-responses-table')]//tbody//td[contains(@class, 'response-col_description')]//code")
USER_STATUS_CODE = (By.XPATH, "//div[contains(@id, 'operations-user-post_team_')]//table[contains(@class, 'live-responses-table')]//tbody//td[contains(@class, 'response-col_status')]")
# Данные для входа
LOGIN_DATA = [Keys.BACKSPACE, Keys.ENTER, '\t"password": "Abc1205",', Keys.ENTER, '\t"username": "muts"', Keys.ENTER, '}']
USER_RAW_DATA = """
"avatar": "🔍",
"description": "QAинженер с фокусом на автоматизированное тестирование и CI/CD.",
"joinDate": "2023-10-01T20:00:00Z",
"motto": "Тестирование — это поиск истины.",
"name": "Андрей Васильев",
"password": "TestPas1205",
"projects": ["Web App Testing", "API Automation"],
"role": "QA Engineer",
"skills": ["Selenium", "JUnit", "Postman", "CI/CD", "TestRail"],
"speciality": "Good user",
"username": "andrey_user"
}
"""
USER_DATA = [Keys.BACKSPACE, USER_RAW_DATA]
def __init__(self, driver: WebDriver):
super().__init__(driver)
@allure.step('Открытие страницы администратора')
def open_admin_page(self):
self.open('http://localhost:8080/swagger/index.html')
@allure.step('Проверка наличия блока логина')
def is_login_block_visible(self):
return self.is_visible(self.LOGIN_BLOCK)
@allure.step('Проверка наличия кнопки "try it out"')
def is_login_tryitout_btn_visible(self):
return self.is_visible(self.LOGIN_TRYITOUT_BTN)
@allure.step('Проверка наличия поля ввода')
def is_login_textarea_visible(self):
return self.is_visible(self.LOGIN_TEXTAREA)
@allure.step('Проверка наличия кнопки выполнения запроса')
def is_login_execute_btn_visible(self):
return self.is_visible(self.LOGIN_EXECUTE_BTN)
@allure.step('Нажатие на блок логина')
def click_login_block(self):
self.click(self.LOGIN_BLOCK)
@allure.step('Нажатие на кнопку "try it out"')
def click_login_tryitout_btn(self):
self.click(self.LOGIN_TRYITOUT_BTN)
@allure.step('Ввод текста в поле логина')
def enter_text_to_login_textarea(self, text: list):
field = self.find_element(self.LOGIN_TEXTAREA)
field.clear()
for event in text:
field.send_keys(event)
@allure.step('Нажатие на кнопку выполнения запроса')
def click_login_execute_btn(self):
self.click(self.LOGIN_EXECUTE_BTN)
@allure.step('Получение статуса выполнения входа')
def get_login_status(self):
return self.get_text(self.LOGIN_STATUS_CODE)
@allure.step('Проверка наличия блока пользователя')
def is_user_block_visible(self):
return self.is_visible(self.USER_BLOCK)
@allure.step('Проверка наличия кнопки "try it out"')
def is_user_tryitout_btn_visible(self):
return self.is_visible(self.USER_TRYITOUT_BTN)
@allure.step('Проверка наличия поля ввода')
def is_user_textarea_visible(self):
return self.is_visible(self.USER_TEXTAREA)
@allure.step('Проверка наличия кнопки выполнения запроса')
def is_user_execute_btn_visible(self):
return self.is_visible(self.USER_EXECUTE_BTN)
@allure.step('Нажатие на блок пользователя')
def click_user_block(self):
self.click(self.USER_BLOCK)
@allure.step('Нажатие на кнопку "try it out"')
def click_user_tryitout_btn(self):
self.click(self.USER_TRYITOUT_BTN)
@allure.step('Ввод текста в поле добавления пользователя')
def enter_text_to_user_textarea(self, text: list):
field = self.find_element(self.USER_TEXTAREA)
field.clear()
for event in text:
field.send_keys(event)
@allure.step('Нажатие на кнопку выполнения запроса')
def click_user_execute_btn(self):
self.click(self.USER_EXECUTE_BTN)
@allure.step('Получение ответа на запрос создания пользователя')
def get_user_response(self):
elem = self.find_element(self.USER_RESPONSE)
return elem.get_attribute('innerText')
@allure.step('Получение статуса создания пользователя')
def get_user_status(self):
return self.get_text(self.USER_STATUS_CODE)

View file

@ -0,0 +1,99 @@
from selenium.webdriver import ActionChains
from selenium.webdriver.chrome.webdriver import WebDriver
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.common.exceptions import TimeoutException, NoSuchElementException
import allure
import logging
logger = logging.getLogger(__name__)
class BasePage:
def __init__(self, driver: WebDriver):
self.driver = driver
self.wait = WebDriverWait(driver, 10)
self.logger = logger
@allure.step("Открыть URL: {url}")
def open(self, url):
self.driver.get(url)
self.logger.info(f"Открыта страница: {url}")
@allure.step("Обновить страницу")
def refresh(self):
self.driver.refresh()
self.logger.info("Страница обновлена")
@allure.step("Найти элемент: {locator}")
def find_element(self, locator):
try:
element = self.wait.until(EC.visibility_of_element_located(locator))
self.logger.debug(f"Элемент найден: {locator}")
return element
except TimeoutException:
self.logger.error(f"Элемент не найден: {locator}")
allure.attach(
self.driver.get_screenshot_as_png(),
name="element_not_found",
attachment_type=allure.attachment_type.PNG
)
raise
@allure.step("Кликнуть на элемент: {locator}")
def click(self, locator):
element = self.find_element(locator)
element.click()
self.logger.info(f"Клик по элементу: {locator}")
@allure.step("Ввести текст '{text}' в элемент: {locator}")
def type_text(self, locator, text):
element = self.find_element(locator)
element.clear()
element.send_keys(text)
self.logger.info(f"Введен текст '{text}' в элемент: {locator}")
@allure.step("Получить текст элемента: {locator}")
def get_text(self, locator):
element = self.find_element(locator)
text = element.text
self.logger.info(f"Получен текст '{text}' из элемента: {locator}")
return text
@allure.step("Проверить, что элемент видим: {locator}")
def is_visible(self, locator):
try:
element = self.find_element(locator)
is_displayed = element.is_displayed()
self.logger.info(f"Элемент {locator} видим: {is_displayed}")
return is_displayed
except (TimeoutException, NoSuchElementException):
return False
@allure.step("Получить текущий URL")
def get_current_url(self):
url = self.driver.current_url
self.logger.info(f"Текущий URL: {url}")
return url
@allure.step("Сделать скриншот")
def take_screenshot(self, name="screenshot"):
screenshot = self.driver.get_screenshot_as_png()
allure.attach(
screenshot,
name=name,
attachment_type=allure.attachment_type.PNG
)
self.logger.info(f"Скриншот сохранен: {name}")
@allure.step("Ожидание загрузки элемента: {locator}")
def wait_for_element(self, locator, timeout=10):
wait = WebDriverWait(self.driver, timeout)
element = wait.until(EC.visibility_of_element_located(locator))
self.logger.info(f"Элемент загружен: {locator}")
return element
@allure.step("Скролл до элемента")
def scroll_to_element(self, locator):
element = self.find_element(locator)
self.driver.execute_script("arguments[0].scrollIntoView({block: 'center'});", element)

View file

@ -0,0 +1,92 @@
import allure
from selenium.webdriver.common.by import By
from config.ui_config import UIConfig
from tests.ui.pages.base_page import BasePage
class HomePage(BasePage):
# Локаторы
LOGO = (By.CLASS_NAME, "logo-text")
TEAM_TITLE = (By.XPATH, "//div[contains(@class, 'team-panel')]//h3")
ARTICLE_TITLE = (By.XPATH, "//div[contains(@class, 'blog-panel')]//h3")
TIME = (By.ID, "current-time")
TEAM_SECTION = (By.CLASS_NAME, "team-carousel")
ARTICLE_SECTION = (By.CLASS_NAME, "articles-list")
TEAM_NOT_FOUND = (By.XPATH, "//div[contains(@class, 'team-carousel')]//div[contains(@class, 'empty-state')]/span[not(contains(@class, 'empty-icon'))]")
ARTICLE_NOT_FOUND = (By.XPATH, "//div[contains(@class, 'articles-list')]//div[contains(@class, 'empty-state')]/span[not(contains(@class, 'empty-icon'))]")
AUTHOR_CARD = (By.CLASS_NAME, "team-member-card")
ARTICLE_CARD = (By.CLASS_NAME, "article-preview")
TEAM_VIEW_ALL = (By.XPATH, "//div[contains(@class, 'team-panel')]//a[contains(@class, 'view-all')]")
ARTICLE_VIEW_ALL = (By.XPATH, "//div[contains(@class, 'blog-panel')]//a[contains(@class, 'view-all')]")
def __init__(self, driver):
super().__init__(driver)
@allure.step("Открытие домашней страницы")
def open_home_page(self):
self.open(UIConfig.UI_BASE_URL)
@allure.step("Проверка большого лого")
def check_home_logo(self):
text = self.get_text(self.LOGO)
assert text == "Team"
return text
@allure.step("Проверка заголовка списка команды")
def check_home_team(self):
text = self.get_text(self.TEAM_TITLE)
assert text == "MEET THE TEAM"
return text
@allure.step("Проверка заголовка списка постов")
def check_home_posts(self):
text = self.get_text(self.ARTICLE_TITLE)
assert text == "LATEST ARTICLES"
return text
@allure.step("Проверить наличие раздела 'Meet the Team'")
def is_meet_the_team_section_displayed(self):
return self.is_visible(self.TEAM_SECTION)
@allure.step("Проверить наличие раздела 'Latest Articles'")
def is_latest_articles_section_displayed(self):
return self.is_visible(self.ARTICLE_SECTION)
@allure.step("Проверить сообщение 'No team members found'")
def check_no_team_members_message(self):
message = self.get_text(self.TEAM_NOT_FOUND)
assert "No team members found" in message, \
f"Ожидалось сообщение 'No team members found', получено '{message}'"
return message
@allure.step("Проверить сообщение 'No articles found'")
def check_no_articles_message(self):
message = self.get_text(self.ARTICLE_NOT_FOUND)
assert "No articles found" in message, \
f"Ожидалось сообщение 'No articles found', получено '{message}'"
return message
@allure.step("Проверить наличие карточки автора")
def is_member_card_displayed(self):
return self.is_visible(self.AUTHOR_CARD)
@allure.step("Проверить наличие карточки статьи")
def is_article_card_displayed(self):
return self.is_visible(self.ARTICLE_CARD)
@allure.step("Проверить видимость кнопки VIEW ALL TEAM")
def is_team_view_all_displayed(self):
return self.is_visible(self.TEAM_VIEW_ALL)
@allure.step("Проверить видимость кнопки VIEW ALL TEAM")
def is_article_view_all_displayed(self):
return self.is_visible(self.ARTICLE_VIEW_ALL)
@allure.step("Проверить кликабельность кнопки VIEW ALL TEAM")
def click_view_all_team_button(self):
self.click(self.TEAM_VIEW_ALL)
@allure.step("Проверить кликабельность кнопки VIEW ALL TEAM")
def click_view_all_article_button(self):
self.click(self.ARTICLE_VIEW_ALL)

158
tests/ui/test_admin_page.py Normal file
View file

@ -0,0 +1,158 @@
import json
import time
import allure
import pytest
from selenium.webdriver.chrome.webdriver import WebDriver
from config.ui_config import UIConfig
from tests.api.utils.api_client import APIClient
from tests.ui.pages.admin_page import AdminPage
@allure.epic("Страница администратора")
@allure.feature("Основной функционал")
class TestAdminPage:
@allure.story("Загрузка страницы")
@allure.title("Проверка успешной загрузки страницы")
@allure.severity(allure.severity_level.BLOCKER)
@pytest.mark.smoke
@pytest.mark.ui
def test_page_load_succesfull(self, driver):
admin_page = AdminPage(driver)
with allure.step('1. Открыть страницу'):
admin_page.open_admin_page()
admin_page.take_screenshot("admin_page")
@allure.story("Структура страницы")
@allure.title("Проверка отображения элементов")
@allure.severity(allure.severity_level.BLOCKER)
@pytest.mark.smoke
@pytest.mark.ui
def test_page_elements_visiblity(self, driver):
admin_page = AdminPage(driver)
with allure.step('1. Открыть страницу'):
admin_page.open_admin_page()
admin_page.take_screenshot("admin_page")
with allure.step('2. Проверка наличия кнопки блока логина'):
assert admin_page.is_login_block_visible(), 'Кнопка блока логина не отобразилась'
with allure.step('3. Проверка невидимости кнопки "try it out"'):
assert not admin_page.is_login_tryitout_btn_visible(), 'Кнопка не должна быть видима'
with allure.step('4. Проверка невидимости поля ввода'):
assert not admin_page.is_login_textarea_visible(), 'Поле ввода не должно быть видимо'
with allure.step('5. Проверка невидимости кнопки выполнения запроса'):
assert not admin_page.is_login_execute_btn_visible(), 'Кнопка выполнения запроса не должна быть видимой'
@allure.story("Функциональность")
@allure.title("Проверка функциональности страницы")
@allure.severity(allure.severity_level.BLOCKER)
@pytest.mark.smoke
@pytest.mark.ui
def test_page_login_process(self, driver: WebDriver):
admin_page = AdminPage(driver)
with allure.step('1. Открыть страницу'):
admin_page.open_admin_page()
admin_page.wait_for_element(admin_page.LOGIN_BLOCK)
admin_page.scroll_to_element(admin_page.LOGIN_BLOCK)
admin_page.take_screenshot("admin_page")
with allure.step('2. Нажать на блок логина'):
admin_page.click_login_block()
assert admin_page.is_login_tryitout_btn_visible(), 'Кнопка "try it out" должна быть видимой'
with allure.step('3. Нажать на кнопку "try it out"'):
admin_page.click_login_tryitout_btn()
assert admin_page.is_login_textarea_visible(), 'Поле ввода должно быть видимым'
assert admin_page.is_login_execute_btn_visible(), 'Кнопка выполнения запроса должна быть видимой'
admin_page.scroll_to_element(admin_page.LOGIN_TRYITOUT_BTN)
admin_page.take_screenshot('admin_page_login_block')
with allure.step('4. Ввод данных для входа'):
admin_page.scroll_to_element(admin_page.LOGIN_TEXTAREA)
admin_page.take_screenshot("admin_page_login_area_unchanged")
admin_page.enter_text_to_login_textarea(admin_page.LOGIN_DATA)
time.sleep(0.5)
admin_page.take_screenshot("admin_page_login_text_entered")
with allure.step('5. Выполнение запроса'):
admin_page.click_login_execute_btn()
time.sleep(2.0)
admin_page.scroll_to_element(admin_page.LOGIN_STATUS_CODE)
admin_page.take_screenshot("admin_page_login_request_executed")
with allure.step('6. Проверка статуса выполнения запроса'):
admin_page.scroll_to_element(admin_page.LOGIN_STATUS_CODE)
assert int(admin_page.get_login_status()) == 200
@allure.story("Добавление пользователя")
@allure.title("Проверка функции добавления пользователя")
@allure.severity(allure.severity_level.BLOCKER)
@pytest.mark.smoke
@pytest.mark.ui
def test_page_user_adding(self, driver: WebDriver, auth_admin: APIClient):
admin_page = AdminPage(driver)
user_id = ''
with allure.step('1. Открыть страницу'):
admin_page.open_admin_page()
admin_page.wait_for_element(admin_page.USER_BLOCK)
admin_page.scroll_to_element(admin_page.USER_BLOCK)
admin_page.take_screenshot("admin_page")
with allure.step("2. Авторизоваться"):
admin_page.click_login_block()
admin_page.click_login_tryitout_btn()
admin_page.enter_text_to_login_textarea(admin_page.LOGIN_DATA)
time.sleep(0.5)
admin_page.click_login_execute_btn()
time.sleep(2.0)
admin_page.scroll_to_element(admin_page.LOGIN_STATUS_CODE)
admin_page.take_screenshot("admin_page_login_request_executed")
with allure.step('3. Нажать на блок пользователя'):
admin_page.click_user_block()
assert admin_page.is_user_tryitout_btn_visible(), 'Кнопка "try it out" должна быть видимой'
with allure.step('4. Нажать на кнопку "try it out"'):
admin_page.click_user_tryitout_btn()
assert admin_page.is_user_textarea_visible(), 'Поле ввода должно быть видимым'
assert admin_page.is_user_execute_btn_visible(), 'Кнопка выполнения запроса должна быть видимой'
admin_page.scroll_to_element(admin_page.USER_TRYITOUT_BTN)
admin_page.take_screenshot('admin_page_user_block')
with allure.step('5. Ввод данных для регистрации пользователя'):
admin_page.scroll_to_element(admin_page.USER_TEXTAREA)
admin_page.take_screenshot("admin_page_user_area_unchanged")
admin_page.enter_text_to_user_textarea(admin_page.USER_DATA)
time.sleep(0.5)
admin_page.take_screenshot("admin_page_user_text_entered")
with allure.step('6. Выполнение запроса'):
admin_page.click_user_execute_btn()
time.sleep(2.0)
admin_page.scroll_to_element(admin_page.USER_STATUS_CODE)
admin_page.take_screenshot("admin_page_user_request_executed")
with allure.step('7. Проверка статуса выполнения запроса'):
admin_page.scroll_to_element(admin_page.LOGIN_STATUS_CODE)
assert int(admin_page.get_login_status()) == 200
with allure.step('8. Сохраняем id пользователя для удаления'):
content = admin_page.get_user_response()
user_id = json.loads(content)['id']
with allure.step('9. Проверка, что пользователь добавлен на сайт'):
admin_page.open(UIConfig.get_url('team'))
time.sleep(2)
admin_page.take_screenshot('team_page_user')
with allure.step('10. Удаляем пользователя через API'):
auth_admin.delete_user(user_id)

224
tests/ui/test_home_page.py Normal file
View file

@ -0,0 +1,224 @@
import pytest
import allure
from selenium.webdriver.chrome.webdriver import WebDriver
from config.ui_config import UIConfig
import time
from tests.ui.pages.home_page import HomePage
@allure.epic("Домашняя страница")
@allure.feature("Основной функционал")
class TestTeamPage:
@allure.story("Загрузка страницы")
@allure.title("Проверка успешной загрузки страницы")
@allure.severity(allure.severity_level.BLOCKER)
@pytest.mark.smoke
@pytest.mark.ui
def test_page_load_successfully(self, driver):
"""
Тест проверяет успешную загрузку страницы Team
"""
home_page = HomePage(driver)
with allure.step("1. Открыть страницу"):
home_page.open_home_page()
home_page.take_screenshot("home_page_loaded")
with allure.step("2. Проверить заголовок окна браузера"):
page_title = driver.title
allure.attach(page_title, name="page_title", attachment_type=allure.attachment_type.TEXT)
assert page_title, "Заголовок страницы пустой"
@allure.story("Контент страницы")
@allure.title("Проверка текстового содержания страницы")
@allure.severity(allure.severity_level.CRITICAL)
@pytest.mark.regression
def test_page_content_validation(self, driver):
"""
Тест проверяет корректность текстового содержания страницы
"""
home_page = HomePage(driver)
with allure.step("1. Открыть страницу"):
home_page.open_home_page()
with allure.step("2. Проверить заголовок"):
title = home_page.get_text(home_page.LOGO)
assert title == "Team", f"Неверный заголовок: {title}"
with allure.step("4. Проверить сообщения о пустом состоянии"):
team_message = home_page.check_no_team_members_message()
articles_message = home_page.check_no_articles_message()
allure.attach(
f"Сообщение о команде: {team_message}\nСообщение о статьях: {articles_message}",
name="empty_state_messages",
attachment_type=allure.attachment_type.TEXT
)
with allure.step("5. Сделать скриншот всей страницы"):
driver.execute_script("window.scrollTo(0, document.body.scrollHeight);")
time.sleep(1)
home_page.take_screenshot("full_page_content")
@allure.story("Структура страницы")
@allure.title("Проверка структуры и порядка элементов")
@allure.severity(allure.severity_level.NORMAL)
@pytest.mark.ui
def test_page_structure(self, driver):
"""
Тест проверяет правильность структуры расположения элементов
"""
home_page = HomePage(driver)
with allure.step("1. Открыть страницу Team"):
home_page.open_home_page()
with allure.step("2. Проверить наличие всех разделов"):
assert home_page.is_meet_the_team_section_displayed(), \
"Раздел 'MEET THE TEAM' не отображается"
assert home_page.is_latest_articles_section_displayed(), \
"Раздел 'LATEST ARTICLES' не отображается"
with allure.step("3. Проверить кнопку VIEW ALL team"):
button = home_page.is_team_view_all_displayed()
assert button, "Кнопка не отобразилась"
with allure.step("4. Проверить кнопку VIEW ALL article"):
button = home_page.is_article_view_all_displayed()
assert button, "Кнопка не отобразилась"
@allure.story("Функциональность")
@allure.title("Проверка кликабельности элементов")
@allure.severity(allure.severity_level.NORMAL)
@pytest.mark.regression
def test_elements_interactivity(self, driver):
"""
Тест проверяет кликабельность интерактивных элементов
"""
home_page = HomePage(driver)
with allure.step("1. Открыть страницу Team"):
home_page.open_home_page()
with allure.step("2. Проверить кликабельность кнопки VIEW ALL team"):
button_team = home_page.find_element(home_page.TEAM_VIEW_ALL)
assert button_team.is_enabled(), "Кнопка VIEW ALL не активна"
# Проверяем, что это ссылка
tag_name = button_team.tag_name
assert tag_name.lower() in ['a', 'button'], \
f"Элемент не является ссылкой или кнопкой (тег: {tag_name})"
with allure.step("3. Проверить кликабельность кнопки VIEW ALL article"):
button_article = home_page.find_element(home_page.ARTICLE_VIEW_ALL)
assert button_article.is_enabled(), "Кнопка VIEW ALL не активна"
# Проверяем, что это ссылка
tag_name = button_article.tag_name
assert tag_name.lower() in ['a', 'button'], \
f"Элемент не является ссылкой или кнопкой (тег: {tag_name})"
with allure.step("4. Навести курсор на кнопку VIEW ALL team"):
from selenium.webdriver.common.action_chains import ActionChains
actions = ActionChains(driver)
actions.move_to_element(button_team).perform()
time.sleep(0.5)
# Проверяем изменение стиля (опционально)
home_page.take_screenshot("team_view_all_button_hover")
with allure.step("4. Навести курсор на кнопку VIEW ALL article"):
from selenium.webdriver.common.action_chains import ActionChains
actions = ActionChains(driver)
actions.move_to_element(button_article).perform()
time.sleep(0.5)
# Проверяем изменение стиля (опционально)
home_page.take_screenshot("article_view_all_button_hover")
@allure.story("Навигация")
@allure.title("Проверка поведения кнопки VIEW ALL")
@allure.severity(allure.severity_level.CRITICAL)
@pytest.mark.smoke
def test_view_all_button_functionality(self, driver: WebDriver):
"""
Тест проверяет функциональность кнопки VIEW ALL
"""
home_page = HomePage(driver)
with allure.step("1. Открыть страницу Team"):
home_page.open_home_page()
initial_url = driver.current_url
allure.attach(initial_url, name="initial_url", attachment_type=allure.attachment_type.TEXT)
with allure.step("2. Нажать кнопку VIEW ALL team"):
home_page.click_view_all_team_button()
time.sleep(2) # Ждем загрузки
new_url = driver.current_url
allure.attach(new_url, name="new_url", attachment_type=allure.attachment_type.TEXT)
with allure.step("3. Проверить изменение URL"):
# Проверяем, что URL изменился (или остался тем же, если это anchor link)
assert new_url == UIConfig.get_url("team"), "Совершен переход на неверную страницу"
with allure.step("4. Возвращаемся на главную"):
driver.back()
with allure.step("2. Нажать кнопку VIEW ALL article"):
home_page.click_view_all_article_button()
time.sleep(2) # Ждем загрузки
new_url = driver.current_url
allure.attach(new_url, name="new_url", attachment_type=allure.attachment_type.TEXT)
with allure.step("3. Проверить изменение URL"):
# Проверяем, что URL изменился (или остался тем же, если это anchor link)
assert new_url == UIConfig.get_url("blog"), "Совершен переход на неверную страницу"
@allure.story("Контент страницы")
@allure.title("Проверка появления карточек")
@allure.severity(allure.severity_level.CRITICAL)
@pytest.mark.regression
def test_cards_visiblity(self, driver, gen_data_fixture):
"""
Тест проверяет появление предложенных карточек
"""
home_page = HomePage(driver)
with allure.step("1. Открыть страницу"):
home_page.open_home_page()
with allure.step("2. Проверка существования карточки автора"):
card = home_page.is_member_card_displayed()
assert card, "Карточка пользователя не отобразилась"
with allure.step("3. Проверка реакции карточки на наведение"):
from selenium.webdriver.common.action_chains import ActionChains
member_card = home_page.find_element(home_page.AUTHOR_CARD)
actions = ActionChains(driver)
actions.move_to_element(member_card).perform()
time.sleep(0.5)
home_page.take_screenshot("Member_card_hower_reaction")
with allure.step("4. Проверка существования карточки статьи"):
card = home_page.is_article_card_displayed()
assert card, "Карточка статьи не отобразилась"
with allure.step("5. Проверка реакции карточки на наведение"):
from selenium.webdriver.common.action_chains import ActionChains
member_card = home_page.find_element(home_page.ARTICLE_CARD)
actions = ActionChains(driver)
actions.move_to_element(member_card).perform()
time.sleep(0.5)
home_page.take_screenshot("Member_card_hower_reaction")

View file

0
tests/ui/utils/wait.py Normal file
View file