Metadata-Version: 2.5
Name: klab-pytest-toolkit-web
Version: 1.3.0
Summary: Pytest web fixtures for the Klab Pytest Toolkit
Project-URL: Changelog, https://github.com/klab365/klab-pytest-toolkit/blob/main/CHANGELOG.md
Project-URL: Repository, https://github.com/klab365/klab-pytest-toolkit
Project-URL: Issues, https://github.com/klab365/klab-pytest-toolkit/issues
Author-email: Burak Kizilkaya <burak.kizilkaya@outlook.com>
License-Expression: MIT
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Requires-Python: <4,>=3.11
Requires-Dist: grpcio-reflection>=1.66.0
Requires-Dist: grpcio-tools>=1.66.0
Requires-Dist: grpcio>=1.66.0
Requires-Dist: jsonschema>=4.25.1
Requires-Dist: playwright>=1.57.0
Requires-Dist: pytest>=8.3.5
Requires-Dist: requests>=2.32.5
Description-Content-Type: text/markdown

# Klab Pytest Toolkit - Web

[![PyPI](https://img.shields.io/pypi/v/klab-pytest-toolkit-web)](https://pypi.org/project/klab-pytest-toolkit-web/)
[![Python](https://img.shields.io/pypi/pyversions/klab-pytest-toolkit-web)](https://pypi.org/project/klab-pytest-toolkit-web/)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](../../LICENSE)

Reusable web-testing components for pytest.
The goal is to allow testers to easily test web applications (HTML, JSON, REST API) with reusable components.

At the moment the package provides the following components:

- `ResponseValidatorFactory`: Factory for creating JSON response validator instances with custom configurations.
- `ApiClientFactory`: Factory for creating different API client instances.
  - REST API client for making HTTP requests to RESTful services.
  - gRPC client for making gRPC calls to gRPC services.
- `WebClientFactory`: Factory for creating web client instances for browser automation.
  - Playwright-based web client for end-to-end testing of web applications.

The factories are plain classes: instantiate them directly (or from within your
own fixtures) rather than relying on auto-registered pytest fixtures.

## Installation

```bash
pip install klab-pytest-toolkit-web
```

## Usage

This package is a **library of factory classes**, not a set of auto-registered
pytest fixtures. The typical pattern is:

1. Import the factory you need.
2. Instantiate it and create a configured client/validator.
3. Use it in your test — either directly or wrapped in your own
   `@pytest.fixture` (recommended, so it is created once and cleaned up via a
   context manager).

```python
from klab_pytest_toolkit_web import ApiClientFactory, RestApiClient


@pytest.fixture
def rest_api_client() -> RestApiClient:
    """Provide a ready-to-use REST API client to tests."""
    with ApiClientFactory().create_rest_client(
        base_url="https://api.example.com",
        headers={"Authorization": "Bearer token"},
    ) as client:
        yield client
```

The sections below describe each factory and its components.

### JSON Response Validator

**Create the fixture**

The factory class `ResponseValidatorFactory` is a plain class; use it directly
(or from within your own fixture).

```python
from klab_pytest_toolkit_web import JsonResponseValidator, ResponseValidatorFactory

@pytest.fixture
def json_validator_user_schema() -> JsonResponseValidator:
    """Fixture to provide a JSON response validator for user schema."""
    user_schema = {
        "type": "object",
        "properties": {
            "id": {"type": "integer"},
            "name": {"type": "string"},
        },
        "required": ["id", "name"]
    }
    return ResponseValidatorFactory().create_json_validator(schema=user_schema)
```

**Functions**

The validator contains one main function to validate a response against the schema. Below is an example of how to use the validator in a test.

```python
def test_user_api(json_validator_user_schema):
    """Test user API response validation."""
    response_data = {
        "id": 1,
        "name": "John Doe"
    }
    assert json_validator_user_schema.validate_response(response_data)
```

### REST API Client

**Create the fixture**

Use the factory class `ApiClientFactory` directly. To pass the URL or other
header information, configure it directly in your fixture (for example from
environment variables or a testcontainer URL).

```python
from klab_pytest_toolkit_web import ApiClientFactory, RestApiClient

@pytest.fixture(scope="session")
def httpbin_container():
    """Fixture to provide an HTTPBin container for testing."""

    with DockerContainer("kennethreitz/httpbin:latest") as httpbin:
        httpbin.with_exposed_ports(80)
        httpbin.waiting_for(HttpWaitStrategy(path="/get", port=80).for_status_code(200))
        httpbin.start()
        port = httpbin.get_exposed_port(80)
        base_url = f"http://localhost:{port}"
        yield base_url

@pytest.fixture
def rest_api_client(httpbin_container) -> RestApiClient:
    """Fixture to provide a REST API client."""
    return ApiClientFactory().create_rest_client(base_url=httpbin_container)
```

**Functions** 

The REST API client provides functions to make HTTP requests. These are some examples:

Each request method accepts an optional per-request ``timeout`` (in seconds).
The client also has a default timeout of 30 seconds that applies when no
per-request timeout is given, so a hanging endpoint cannot block a test forever.

```python
def test_get_request(rest_api_client: RestApiClient):
    """Test basic GET request with query parameters."""
    response = rest_api_client.get("/get", params={"test": "value"})

    assert response.status_code == 200
    json_data = response.json()
    assert json_data["args"]["test"] == "value"

def test_post_request(rest_api_client: RestApiClient):
    """Test basic POST request with JSON body."""
    response = rest_api_client.post("/post", payload={"key": "value"})

    assert response.status_code == 200
    json_data = response.json()
    assert json_data["json"]["key"] == "value"

def test_update_request(rest_api_client: RestApiClient):
    """Test basic PUT request with JSON body."""
    response = rest_api_client.put("/put", payload={"update": "data"})

    assert response.status_code == 200
    json_data = response.json()
    assert json_data["json"]["update"] == "data"

def test_delete_request(rest_api_client: RestApiClient):
    """Test basic DELETE request."""
    response = rest_api_client.delete("/delete")

    assert response.status_code == 200
    json_data = response.json()
    assert json_data["url"].endswith("/delete")
```

### gRPC Client

**Create the fixture**

Use the factory class `ApiClientFactory` directly.
You can create a gRPC client fixture as shown below:

```python
from klab_pytest_toolkit_web import ApiClientFactory, GrpcClient

@pytest.fixture
def grpc_client() -> GrpcClient:
    """Fixture to provide a gRPC client."""
    with ApiClientFactory().create_grpc_client(
        target="localhost:50051",
        proto_file="path/to/your/service.proto"
    ) as client:
        yield client
```

**Functions**

The `GrpcClient` provides gRPC call functionality by dynamically invoking methods defined in the provided proto file. It is assumed that the proto file defines a service with methods like `GetUser`. You will not get any code completion in your IDE since the methods are dynamically resolved at runtime, but you do get an error with helpful suggestions if a method does not exist.

```python
def test_grpc_get_user(grpc_client: GrpcClient):
    """Test gRPC GetUser call."""
    response = grpc_client.GetUser(id=123)

    assert response.id == 123
    assert response.name == "John Doe"
```

### Playwright Web Client

**Create the fixture**

Use the factory class `WebClientFactory` directly. For playwright, you might
install the browsers first by running `playwright install` in your environment.
You can create a Playwright web client fixture as shown below:

```python
from klab_pytest_toolkit_web import WebClient, WebClientFactory

@pytest.fixture
def web_client() -> WebClient:
    """Fixture to provide a Playwright web client."""
    with WebClientFactory.create_client(client_type="playwright", headless=True) as client:
        yield client
```

**Functions**

The `WebClient` provides a variety of functions for browser automation. Refer to the api of the instance.
Here are some examples of common operations:

```python
def test_navigate_and_click(web_client):
    """Test navigation and clicking a button."""
    web_client.navigate_to("https://example.com")
    web_client.click("#start-button")
    assert web_client.get_text("#result") == "Started"

def test_form_submission(web_client):
    """Test form submission."""
    web_client.navigate_to("https://example.com/form")
    web_client.fill("#name", "Test User")
    web_client.fill("#email", "max@muster.com")
    web_client.click("#submit-button")
    assert web_client.get_text("#confirmation") == "Thank you for your submission!"

def test_wait_for_element(web_client):
    """Test waiting for an element to appear."""
    web_client.navigate_to("https://example.com/dynamic")
    web_client.wait_for_element("#dynamic-content", timeout=10000)  # milliseconds
    assert web_client.get_text("#dynamic-content") == "Loaded Content"
```

## Examples

See the test files for comprehensive examples.

## Best Practices

### Use Testcontainers for Isolated Environments

When testing web applications, it's recommended to use testcontainers to create isolated environments for your services.
This ensures that your tests are reproducible and do not interfere with each other.

## Links

- [Source code](https://github.com/klab365/klab-pytest-toolkit/tree/main/packages/klab-pytest-toolkit-web)
- [PyPI](https://pypi.org/project/klab-pytest-toolkit-web/)
- [Issue tracker](https://github.com/klab365/klab-pytest-toolkit/issues)

## License

MIT
