> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://respan.ai/docs/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://respan.ai/docs/_mcp/server.

# Pytest (tracing)

> Trace Pytest sessions and individual tests with Respan, including outcomes, phases, parametrization, nested application spans, and failures.

[Pytest](https://pytest.org/) is a Python testing framework with fixtures, parametrization, plugins, and rich outcome reporting. `respan-instrumentation-pytest` is an installable Pytest plugin that emits one Respan workflow span for each test session and one task span for every test protocol.

The test span remains current across setup, synchronous or asynchronous execution, and teardown. Any Respan spans created by the application under test are therefore nested beneath the correct test.

#### Set up Respan

Create an account at [platform.respan.ai](https://platform.respan.ai) and grab an [API key](https://platform.respan.ai/platform/gateway/api-keys).

Run `npx @respan/cli setup` to set up with your coding agent.

#### Example projects

* [Python package README](https://github.com/RespanAI/respan/tree/main/python-sdks/instrumentations/respan-instrumentation-pytest)
* [Runnable Pytest examples](https://github.com/respanai/respan-example-projects/tree/main/python/tracing/pytest)

#### Tracing

## Setup

#### Install the plugin

```bash
pip install respan-ai respan-instrumentation-pytest pytest
```

Pytest discovers the package through its `pytest11` entry point. Tracing remains opt-in after installation, so a globally installed plugin does not unexpectedly export every test run.

#### Set the Respan API key

```bash
export RESPAN_API_KEY="YOUR_RESPAN_API_KEY"
```

Set `RESPAN_BASE_URL` as well when exporting to a self-hosted or non-default Respan endpoint.

#### Run Pytest with tracing

```bash
pytest --respan-tracing
```

The plugin creates a session workflow automatically and flushes it when the session finishes, including when tests fail.

#### View your trace

Open the [Traces page](https://platform.respan.ai/platform/traces). The default workflow name is derived from the test root; configure a stable name for CI as shown below.

## Configure Pytest

Enable tracing for a repository in `pytest.ini`:

```ini
[pytest]
respan_tracing = true
respan_capture_content = true
respan_workflow_name = checkout_integration_tests
```

Or enable it through environment variables, which is convenient in CI:

```bash
export RESPAN_PYTEST_ENABLED="true"
export RESPAN_PYTEST_WORKFLOW_NAME="checkout_integration_tests"
pytest -q
```

| Setting              | Command line                  | Environment                           | `pytest.ini`                     |
| -------------------- | ----------------------------- | ------------------------------------- | -------------------------------- |
| Enable tracing       | `--respan-tracing`            | `RESPAN_PYTEST_ENABLED=true`          | `respan_tracing = true`          |
| Capture test content | `--respan-capture-content`    | `RESPAN_PYTEST_CAPTURE_CONTENT=true`  | `respan_capture_content = true`  |
| Disable test content | `--no-respan-capture-content` | `RESPAN_PYTEST_CAPTURE_CONTENT=false` | `respan_capture_content = false` |
| Workflow name        | —                             | `RESPAN_PYTEST_WORKFLOW_NAME`         | `respan_workflow_name`           |

## Captured test lifecycle

| Data          | Description                                                                               |
| ------------- | ----------------------------------------------------------------------------------------- |
| Session       | One workflow span with root path, collected count, exit status, and outcome totals.       |
| Test protocol | One task span covering setup, call, and teardown.                                         |
| Outcomes      | Passed, skipped, expected-failure, and failed outcomes.                                   |
| Timing        | Duration for each phase plus total protocol duration.                                     |
| Test context  | Node ID, parametrization, markers, and fixture names when content capture is enabled.     |
| Failures      | Exception, failing phase, OpenTelemetry error status, `status_code`, and `error.message`. |
| xdist         | Each worker emits its own session workflow with worker metadata.                          |

Fixture names may be captured, but fixture return values are never recorded.

## Nested application spans

No decorator is required to trace the test itself. Respan decorators and auto-instrumented library calls made inside a test automatically become children of its `pytest.test` task span.

```python
import pytest
from respan import task


@task(name="calculate_checkout_total")
def calculate_total(amount: int, tax_rate: float) -> float:
    return round(amount * (1 + tax_rate), 2)


@pytest.mark.parametrize(
    ("amount", "tax_rate", "expected"),
    [(100, 0.05, 105.0), (80, 0.10, 88.0)],
)
def test_checkout_total(amount, tax_rate, expected):
    assert calculate_total(amount, tax_rate) == expected
```

Asynchronous tests are traced by the same protocol hook. Continue using your normal async plugin, such as `pytest-asyncio`:

```python
import asyncio

import pytest


@pytest.mark.asyncio
async def test_async_lookup():
    await asyncio.sleep(0)
    assert {"status": "available"}["status"] == "available"
```

## Content capture

By default, parameter values, marker names, fixture names, and failure text are attached to the test span. Disable them for suites that use secrets or production-like data:

```bash
pytest --respan-tracing --no-respan-capture-content
```

With capture disabled, normalized node IDs, outcomes, phase durations, status codes, and exception types remain visible. Parameter values, fixture and marker lists, raw failure messages, and exception events are omitted.

## Failure behavior

The plugin observes Pytest's result without changing it. A failed setup, assertion, or teardown still produces the normal Pytest exit code, while its span records:

* OpenTelemetry `ERROR` status;
* backend-visible `status_code=500`;
* backend-visible `error.message`;
* the phase that failed and the durations collected so far.

Skipped and expected-failure tests remain non-error outcomes. Session shutdown flushes completed spans even when Pytest exits with a failure.

## Verify plugin discovery

Use Pytest's built-in trace output to confirm that the `respan` entry point is installed:

```bash
pytest --trace-config --respan-tracing
```

The [runnable example suite](https://github.com/respanai/respan-example-projects/tree/main/python/tracing/pytest) includes successful, asynchronous, skipped, expected-failure, content-control, and deterministic failure runs.