Skip to main content

jleechanorg-testing-utils

Generic server/MCP/browser testing utilities for Python projects.

Features

  • HTTP client with request/response capture, credential redaction, and retry logic
  • MCP client for Model Context Protocol (JSON-RPC 2.0) servers
  • Evidence bundles – structured /tmp/<project>/<branch>/iteration_NNN/ directories with git provenance
  • Local server management – subprocess server lifecycle with port reservation
  • Generic test base – abstract BaseTestRunner for server integration tests
  • Browser test base – Playwright-based BrowserTestBase for UI tests
  • HTTP test baseHttpTestBase using requests/httpie for web server tests
  • Server-side loggingLLMCallLogger and HttpRequestLogger that servers can import to capture raw request/response payloads

Installation

pip install jleechanorg-testing-utils

# For browser testing:
pip install "jleechanorg-testing-utils[browser]"

Usage

MCP Server Testing

from testing_utils import MCPClient, BaseTestRunner, TestResult

class SmokeTest(BaseTestRunner):
    TEST_NAME = "mcp_smoke"
    PROJECT_NAME = "myproject"
    DEFAULT_BASE_URL = "http://localhost:8081"

    def run_scenarios(self) -> list[TestResult]:
        tools = self.client.tools_list()
        return [TestResult("tools_list", passed=bool(tools), detail=f"{len(tools)} tools")]

if __name__ == "__main__":
    SmokeTest().run()

HTTP Web Server Testing

from testing_utils.http_test import HttpTestBase

class ApiTest(HttpTestBase):
    TEST_NAME = "api_smoke"
    BASE_URL = "http://localhost:9000"
    PROJECT_NAME = "myproject"

    def run_scenarios(self):
        r = self.get("/health")
        r.assert_ok()
        r.assert_json_key("status", "healthy")

        # httpie-style subprocess
        rc, out, err = self.httpie("GET", "/api/users")
        return [{"name": "health_ok", "passed": r.ok}]

if __name__ == "__main__":
    test = ApiTest()
    results = test.run()

Browser Testing (requires [browser] extra)

from testing_utils.browser import BrowserTestBase

class HomepageTest(BrowserTestBase):
    BASE_URL = "http://localhost:3000"
    TEST_NAME = "homepage_smoke"
    PROJECT_NAME = "myproject"
    HEADLESS = True

    def run_scenarios(self):
        self.page.goto(self.BASE_URL)
        title = self.page.title()
        self.take_screenshot("homepage")
        return [{"name": "page_loads", "passed": bool(title), "detail": title}]

if __name__ == "__main__":
    HomepageTest().run()

Server-Side LLM Logging

Import in your server to capture raw LLM request/response payloads for evidence bundles:

from testing_utils.logging_capture import LLMCallLogger

# Use default env var names:
_llm_logger = LLMCallLogger(
    capture_path_env="RAW_LLM_CAPTURE_PATH",
    enabled_env="CAPTURE_RAW_LLM",
    max_chars_env="CAPTURE_RAW_LLM_MAX_CHARS",
)

def call_llm(provider, model, messages, system_instruction=None):
    _llm_logger.log_request(
        provider=provider,
        model=model,
        request_payload={"messages": messages},
        system_instruction=system_instruction,
    )
    response_text = _actual_llm_call(provider, model, messages)
    _llm_logger.log_response(
        provider=provider,
        model=model,
        response_text=response_text,
    )
    return response_text

Set RAW_LLM_CAPTURE_PATH=/tmp/myproject/llm_captures.jsonl before running tests to enable capture.

Evidence Bundles

from testing_utils.evidence import (
    get_evidence_dir,
    create_evidence_bundle,
    write_with_checksum,
    capture_provenance,
)

# Get branch-scoped evidence directory
evidence_dir = get_evidence_dir("my_test", project_name="myproject")
# → /tmp/myproject/<branch>/my_test/

# Create a structured bundle after running tests
create_evidence_bundle(
    evidence_dir,
    results={"passed": 5, "failed": 0},
    test_name="my_test",
    project_name="myproject",
    http_captures=client.get_captures_as_dict(),
)
# → /tmp/myproject/<branch>/my_test/iteration_001/
#   ├── README.md
#   ├── metadata.json
#   ├── results.json
#   └── request_responses.jsonl

Environment Variables

Variable Module Default Description
RAW_LLM_CAPTURE_PATH logging_capture `` (disabled) Path to JSONL file for LLM captures
RAW_HTTP_CAPTURE_PATH logging_capture `` (disabled) Path to JSONL file for HTTP captures
CAPTURE_RAW_LLM logging_capture true Enable/disable LLM capture
CAPTURE_RAW_LLM_MAX_CHARS logging_capture 20000 Max chars per LLM payload field
TEST_SCREENSHOT_DIR browser branch-scoped /tmp Screenshot output directory
TEST_VIDEO_DIR browser branch-scoped /tmp Video output directory
TEST_RECORD_VIDEO browser false Enable Playwright video recording
TEST_BASE_URL browser, http_test http://localhost:8081 Server URL override

Design Principles

  • No hardcoded project names – everything is parameterized
  • No hardcoded URLs or credentials – all configurable via constructor or env vars
  • Thread-safe – logging capture uses file locks
  • Credential redaction – API keys, JWTs, and auth tokens are automatically redacted in captures
  • Evidence-first – every test run creates a structured evidence bundle with git provenance

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

jleechanorg_testing_utils-0.1.2.tar.gz (36.9 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

jleechanorg_testing_utils-0.1.2-py3-none-any.whl (43.2 kB view details)

Uploaded Python 3

File details

Details for the file jleechanorg_testing_utils-0.1.2.tar.gz.

File metadata

File hashes

Hashes for jleechanorg_testing_utils-0.1.2.tar.gz
Algorithm Hash digest
SHA256 f28344a0699efe1be779a9dc0cccf9adcf800443653293102773ff45a27eb626
MD5 41b2aaefc6499527a5d8eb7cdd7cd4fb
BLAKE2b-256 0451065b67e27ae5028e838ccfde9062c3b1ab661712797064e915980d6b27e5

See more details on using hashes here.

File details

Details for the file jleechanorg_testing_utils-0.1.2-py3-none-any.whl.

File metadata

File hashes

Hashes for jleechanorg_testing_utils-0.1.2-py3-none-any.whl
Algorithm Hash digest
SHA256 1f62bfc307db4c845c9ec0b7526a672cda7766a68f97bd8933b6f3103136d49e
MD5 dba18b20d487d1adceb21b98268e280a
BLAKE2b-256 c99989b0c7bd9b6a453c87c41659faa5f81acb4a0f9855e77fd1e1c3156d6f77

See more details on using hashes here.

Release history Release notifications | RSS feed

This release

0.1.2 This release

2 files

0.1.1

2 files

0.1.0

2 files

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page