Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
23be4b7
first taf draft + tests
aleksei-sobolev-epam Aug 18, 2025
2d86d0d
removed an excessive file
aleksei-sobolev-epam Aug 18, 2025
ab06e3e
menu and search tests
aleksei-sobolev-epam Aug 20, 2025
673a7d3
added selection-deselection test
aleksei-sobolev-epam Aug 20, 2025
909f65f
added datasets creation/deletion tests
aleksei-sobolev-epam Aug 21, 2025
3f2b230
added files creation/deletion tests
aleksei-sobolev-epam Aug 22, 2025
22575f6
added files moving test
aleksei-sobolev-epam Aug 22, 2025
8a9a236
refactoring and jobs creation test
aleksei-sobolev-epam Aug 25, 2025
a2e7bed
small fix for jobs creation test
aleksei-sobolev-epam Aug 26, 2025
01751b2
moved some constants from tests
aleksei-sobolev-epam Aug 26, 2025
a857a40
added categories page test
aleksei-sobolev-epam Aug 26, 2025
2b88871
added users helper, removed uuid from constants
aleksei-sobolev-epam Aug 26, 2025
fce1501
Merge branch 'EPMGPTBADG-260-first-taf-draft' into EPMGPTBADG-261-cat…
aleksei-sobolev-epam Aug 27, 2025
bd6de0c
reports page test
aleksei-sobolev-epam Aug 27, 2025
3ad3e1a
added plugin tests
aleksei-sobolev-epam Aug 28, 2025
7fded78
Merge branch 'EPMGPTBADG-260-first-taf-draft' into EPMGPTBADG-261-cat…
aleksei-sobolev-epam Aug 28, 2025
9391644
added more tests for datasets and files
aleksei-sobolev-epam Aug 28, 2025
8ed84b3
Merge branch 'EPMGPTBADG-260-first-taf-draft' into EPMGPTBADG-261-cat…
aleksei-sobolev-epam Aug 28, 2025
2b1da03
added more jobs tests
aleksei-sobolev-epam Aug 29, 2025
1723cb5
added more jobs tests
aleksei-sobolev-epam Aug 29, 2025
fed15c0
added file download tests
aleksei-sobolev-epam Sep 2, 2025
4d6fd34
Merge branch 'EPMGPTBADG-260-first-taf-draft' into EPMGPTBADG-261-cat…
aleksei-sobolev-epam Sep 2, 2025
ddaffd3
added invalid format upload, clear search and sorting tests
aleksei-sobolev-epam Sep 3, 2025
3678f20
reformatted tests structure
aleksei-sobolev-epam Sep 3, 2025
e79a68f
added select all - unselect all frontend tests
aleksei-sobolev-epam Sep 5, 2025
55f65e4
added icon view - list view frontend tests
aleksei-sobolev-epam Sep 5, 2025
63a9f10
added select-unselect one-by-one tests
aleksei-sobolev-epam Sep 8, 2025
d2b7b7c
added add to dataset - empty field check
aleksei-sobolev-epam Sep 8, 2025
b87eb6e
added some upload wizard tests
aleksei-sobolev-epam Sep 10, 2025
eaffdec
added more upload wizard tests
aleksei-sobolev-epam Sep 11, 2025
ec6a814
added more upload wizard tests
aleksei-sobolev-epam Sep 12, 2025
a8cdf7a
added controls tests
aleksei-sobolev-epam Sep 16, 2025
0acc562
added more frontend tests
aleksei-sobolev-epam Sep 18, 2025
f60353f
added ui deletion and adding to extraction tests
aleksei-sobolev-epam Sep 19, 2025
a4052f7
added dataset adding ui tests
aleksei-sobolev-epam Sep 23, 2025
dba4f95
added remaining plugins tests
aleksei-sobolev-epam Sep 26, 2025
d473f98
new jobs tests + some refactoring + adding steps
aleksei-sobolev-epam Sep 30, 2025
d211c0a
more jobs tests
aleksei-sobolev-epam Oct 2, 2025
ed29ece
added more jobs creation tests + human in the loop
aleksei-sobolev-epam Oct 3, 2025
4d65f98
added some more jobs tests
aleksei-sobolev-epam Oct 7, 2025
385f493
added categories and tasks frontend tests
aleksei-sobolev-epam Oct 21, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions test_automation_framework/.flake8
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
[flake8]
max-line-length = 120
ignore = E203, W503
exclude = .git,__pycache__,build,dist
5 changes: 5 additions & 0 deletions test_automation_framework/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
.env
__pycache__/
*.pyc
.vscode/
.idea/
14 changes: 14 additions & 0 deletions test_automation_framework/.pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
repos:
- repo: https://github.com/psf/black
rev: 25.1.0
hooks:
- id: black
args: [--line-length=120]
language_version: python3

- repo: https://github.com/pycqa/flake8
rev: 7.3.0
hooks:
- id: flake8
args: [--config=test_automation_framework/.flake8]
additional_dependencies: []
47 changes: 47 additions & 0 deletions test_automation_framework/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# BadgerDoc Test Automation Framework

This project is a Python-based **test automation framework** built with [pytest](https://docs.pytest.org/).

## Getting Started

### 1. Install PDM
Make sure you have [PDM](https://pdm-project.org/latest/#installation) installed:

```bash
brew install pdm # macOS
# or
pip install pdm
```

Verify installation:

```bash
pdm --version
```

### 2. Clone the repository

```bash
git clone https://github.com/epam/badgerdoc.git
cd badgerdoc
```

### 3. Install dependencies

```bash
pdm install
```

### 4. Pre-commit hooks

Enable pre-commit to enforce style and linting:
```bash
pre-commit install
```
Now hooks will run automatically before each commit.

### 5. Run tests

```bash
pdm run pytest
```
8 changes: 8 additions & 0 deletions test_automation_framework/config/defaults.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
BASE_URL: "http://demo.badgerdoc.com"
BASE_PORT: 8080
TIMEOUT_SECONDS: 30
MAX_WORKERS: 4
USE_MOCK_LLM: true
LOG_LEVEL: "INFO"
API_USER: "user@example.com"
API_PASS: "changeme"
201 changes: 201 additions & 0 deletions test_automation_framework/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,201 @@
import logging
from logging import getLogger
from typing import Tuple
from playwright.sync_api import expect


import pytest

from settings import load_settings
from helpers.auth.auth_client import AuthClient
from helpers.base_client.base_client import BaseClient
from helpers.datasets.dataset_client import DatasetClient
from helpers.files.file_client import FileClient
from helpers.jobs.jobs_client import JobsClient
from helpers.menu.menu_client import MenuClient
from helpers.category.categories import CategoriesClient
from helpers.users.users import UsersClient
from helpers.reports.reports_client import ReportsClient
from helpers.plugins.plugins_client import PluginsClient

from playwright.sync_api import Page

logger = getLogger(__name__)


def pytest_configure():
logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(name)s: %(message)s")


@pytest.fixture(scope="session")
def settings():
return load_settings()


@pytest.fixture(scope="session")
def tenant(settings) -> str:
return getattr(settings, "TENANT", "demo-badgerdoc")


@pytest.fixture(scope="session")
def base_client(settings) -> BaseClient:
client = BaseClient(f"{settings.BASE_URL}:{settings.BASE_PORT}", timeout=10)
yield client
client.close()


@pytest.fixture(scope="session")
def auth_service(base_client) -> AuthClient:
return AuthClient(base_client)


@pytest.fixture(scope="session")
def auth_token(auth_service, settings) -> Tuple[str, str]:
return auth_service.get_token(settings.API_USER, settings.API_PASS.get_secret_value())


@pytest.fixture
def access_token(auth_token) -> str:
return auth_token[0]


@pytest.fixture
def menu_client(settings, access_token, tenant) -> MenuClient:
return MenuClient(f"{settings.BASE_URL}:{settings.BASE_PORT}", access_token, tenant)


@pytest.fixture
def dataset_client(settings, access_token, tenant) -> DatasetClient:
return DatasetClient(f"{settings.BASE_URL}:{settings.BASE_PORT}", access_token, tenant)


@pytest.fixture
def file_client(settings, access_token, tenant) -> FileClient:
return FileClient(f"{settings.BASE_URL}:{settings.BASE_PORT}", access_token, tenant)


@pytest.fixture
def jobs_client(settings, access_token, tenant) -> JobsClient:
return JobsClient(f"{settings.BASE_URL}:{settings.BASE_PORT}", access_token, tenant)


@pytest.fixture
def reports_client(settings, access_token, tenant) -> ReportsClient:
return ReportsClient(f"{settings.BASE_URL}:{settings.BASE_PORT}", access_token, tenant)


@pytest.fixture
def plugins_client(settings, access_token, tenant) -> PluginsClient:
return PluginsClient(f"{settings.BASE_URL}:{settings.BASE_PORT}", access_token, tenant)


@pytest.fixture
def user_uuid(settings, access_token, tenant) -> str:
users_client = UsersClient(f"{settings.BASE_URL}:{settings.BASE_PORT}", access_token, tenant)
users = users_client.search_users()
return next((u.id for u in users if u.username == "admin"), None)


@pytest.fixture
def categories_client(settings, access_token, tenant) -> CategoriesClient:
return CategoriesClient(f"{settings.BASE_URL}:{settings.BASE_PORT}", access_token, tenant)


@pytest.fixture
def dataset_tracker(dataset_client):
created: list[str] = []
yield created, dataset_client
for name in created:
try:
resp = dataset_client.delete_dataset(name=name)
logger.info(f"[dataset_tracker] Deleted dataset {name}: {resp.get('detail')}")
except Exception as e:
logger.warning(f"[dataset_tracker] Failed to delete dataset {name}: {e}")


@pytest.fixture
def file_tracker(file_client):
created_files: list[dict] = []
yield created_files, file_client
if created_files:
ids = [f["id"] for f in created_files if f.get("id") is not None]
if ids:
try:
result = file_client.delete_files(ids)
logger.info(f"[file_tracker] Deleted files: {ids}, response={result}")
except Exception as e:
logger.warning(f"[file_tracker] Failed to cleanup files {ids}: {e}")


@pytest.fixture
def job_tracker(jobs_client):
created: list[dict] = []
yield created, jobs_client
for job in created:
job_id = job.get("id") or job.get("job_id") or (job.get("job") or {}).get("id")
if not job_id:
continue
try:
jobs_client.post("/jobs/jobs/cancel", json={"id": job_id}, headers=jobs_client._default_headers())
logger.info(f"[job_tracker] Cancelled job {job_id}")
except Exception as e:
logger.warning(f"[job_tracker] Could not cancel job {job_id}: {e}")


@pytest.fixture
def plugins_tracker(plugins_client):
created: list[int] = []
yield created, plugins_client
for id in created:
try:
plugins_client.delete_plugin(plugin_id=id)
logger.info(f"[plugins_tracker] Deleted plugin {id}")
except Exception as e:
logger.warning(f"[plugins_tracker] Failed to delete plugin {id}: {e}")


@pytest.fixture
def logged_in_page(page: Page, settings) -> Page:
page.goto(f"{settings.BASE_URL}:8083/login", timeout=180000)
page.get_by_role("textbox", name="Username").fill("admin")
page.get_by_role("textbox", name="Password").fill("admin")
page.get_by_role("button", name="Login", exact=True).click()
items = page.locator("a[class^='document-card-view-item_card-item']")
expect(items.first).to_be_visible(timeout=100000)
return page


@pytest.fixture
def plugins_page(logged_in_page, settings) -> Page:
page = logged_in_page
page.goto(f"{settings.BASE_URL}:8083/settings/plugins")
row_cells = page.locator("div[role='row'] div[role='cell']:first-child div div")
expect(row_cells.first).to_be_visible(timeout=100000)
return page


@pytest.fixture
def jobs_page(logged_in_page, settings) -> Page:
page = logged_in_page
page.goto(f"{settings.BASE_URL}:8083/jobs")
rows = page.locator("div[role='row']").locator("xpath=..").locator("div[role='row']:not(.uui-table-header-row)")
expect(rows.first).to_be_visible(timeout=5000)
return page


@pytest.fixture
def categories_page(logged_in_page, settings) -> Page:
page = logged_in_page
page.goto(f"{settings.BASE_URL}:8083/categories")
rows = page.locator("div[role='row']").locator("xpath=..").locator("div[role='row']:not(.uui-table-header-row)")
expect(rows.first).to_be_visible(timeout=5000)
return page


@pytest.fixture
def tasks_page(logged_in_page, settings) -> Page:
page = logged_in_page
page.goto(f"{settings.BASE_URL}:8083/my tasks")
rows = page.locator("div[role='row']").locator("xpath=..").locator("div[role='row']:not(.uui-table-header-row)")
expect(rows.first).to_be_visible(timeout=5000)
return page
46 changes: 46 additions & 0 deletions test_automation_framework/helpers/auth/auth_client.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
from __future__ import annotations
from typing import Optional
from pydantic import BaseModel

from helpers.base_client.base_client import BaseClient


class TokenResponse(BaseModel):
access_token: str
refresh_token: str
id_token: Optional[str] = None
scope: Optional[str] = None
session_state: Optional[str] = None
token_type: Optional[str] = None
expires_in: Optional[int] = None


class AuthClient:
def __init__(self, client: BaseClient) -> None:
self.client = client

def get_token(self, username: str, password: str, client_id: str = "admin-cli") -> tuple[str, str]:
resp = self.client.post_json(
"/users/token",
data={
"grant_type": "password",
"username": username,
"password": password,
"client_id": client_id,
},
headers={"Content-Type": "application/x-www-form-urlencoded"},
)
result = TokenResponse.model_validate(resp)
return result.access_token, result.refresh_token

def refresh_token(self, refresh_token: str, client_id: str = "admin-cli") -> tuple[str, str]:
resp = self.client.post_json(
"/users/refresh_token",
json={
"grant_type": "refresh_token",
"client_id": client_id,
"refresh_token": refresh_token,
},
)
result = TokenResponse.model_validate(resp)
return result.access_token, result.refresh_token
Loading