Skip to content

Framework Architecture — Layers & Responsibilities

High-Level Layer Model

┌─────────────────────────────────────────────┐
│               TEST LAYER                    │
│  test cases · scenarios · assertions        │
├─────────────────────────────────────────────┤
│           ABSTRACTION LAYER                 │
│  page objects · API clients · service wraps │
├─────────────────────────────────────────────┤
│          CORE FRAMEWORK LAYER               │
│  drivers · utilities · helpers · builders   │
├─────────────────────────────────────────────┤
│          INFRASTRUCTURE LAYER               │
│  HTTP · browser · gRPC · WebSocket          │
├─────────────────────────────────────────────┤
│             DATA LAYER                      │
│  builders · fixtures · factories            │
└─────────────────────────────────────────────┘

Each layer depends only on the layer below it. No layer skips levels.


Test Layer

What it does: orchestrates a scenario and asserts an outcome.

What it must NOT contain: - HTTP request construction - Element locator strings - Data generation logic - Retry or wait logic

# Good — test layer is clean orchestration
def test_checkout_flow(api_client, user_factory):
    user = user_factory.create_with_payment_method()
    order = api_client.orders.create(user_id=user.id, items=[ITEM_SKU])
    api_client.orders.checkout(order.id)

    assert api_client.orders.get(order.id).status == "COMPLETED"

Abstraction Layer

Page Objects wrap UI pages. Tests call login_page.submit(), not page.click(selector).

API Clients wrap HTTP calls. Tests call api.users.create(email), not requests.post(url, json).

Service Wrappers group related API clients into a cohesive domain interface.

Rules: - One class per domain (users, orders, products) - No assertion logic inside abstractions — they describe capability - Return typed objects, not raw dicts


Core Framework Layer

Drivers — abstract over the communication channel: - BrowserDriver wraps Playwright - HttpDriver wraps httpx - GrpcDriver wraps generated stubs - WsDriver wraps WebSocket client

Utilities — reusable helpers with no domain knowledge: - retry decorator - wait_for helper - logger factory - datetime freezer

Builders — construct test objects with sensible defaults (see Data Layer).


Infrastructure Layer

Direct integration with external tools. No domain logic here.

Component Technology
HTTP client httpx (async)
Browser Playwright
gRPC grpcio + generated stubs
WebSocket websockets

This layer is replaceable. Swapping httpx for another client touches only this layer.


Data Layer

Test Data Builders — fluent, composable objects with unique-by-default fields.

Fixtures — pytest fixtures that create and clean up test data.

Factories — produce instances of framework components (clients, drivers, builders).

# Factory creates configured client — test doesn't care about auth details
@pytest.fixture
def api_client(env_config: EnvConfig) -> ApiClient:
    return ApiClientFactory.create(env_config)

Responsibility Matrix

Concern Test Abstraction Core Infra Data
Assertion
Navigation
HTTP call
Retry logic
Auth headers
Data creation
DB seeding

Dependency Direction

Tests → Abstractions → Core → Infra
Tests → Data Layer

Core and Data layers never import from Tests. Infra never imports from Abstractions. Tests are the only layer allowed to combine everything.