Pytest (tracing)

Pytest 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.

Create an account at platform.respan.ai and grab an API key.

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

Setup

1

Install the plugin

$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.

2

Set the Respan API key

$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.

3

Run Pytest with tracing

$pytest --respan-tracing

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

4

View your trace

Open the Traces page. 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:

1[pytest]
2respan_tracing = true
3respan_capture_content = true
4respan_workflow_name = checkout_integration_tests

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

$export RESPAN_PYTEST_ENABLED="true"
$export RESPAN_PYTEST_WORKFLOW_NAME="checkout_integration_tests"
$pytest -q
SettingCommand lineEnvironmentpytest.ini
Enable tracing--respan-tracingRESPAN_PYTEST_ENABLED=truerespan_tracing = true
Capture test content--respan-capture-contentRESPAN_PYTEST_CAPTURE_CONTENT=truerespan_capture_content = true
Disable test content--no-respan-capture-contentRESPAN_PYTEST_CAPTURE_CONTENT=falserespan_capture_content = false
Workflow nameRESPAN_PYTEST_WORKFLOW_NAMErespan_workflow_name

Captured test lifecycle

DataDescription
SessionOne workflow span with root path, collected count, exit status, and outcome totals.
Test protocolOne task span covering setup, call, and teardown.
OutcomesPassed, skipped, expected-failure, and failed outcomes.
TimingDuration for each phase plus total protocol duration.
Test contextNode ID, parametrization, markers, and fixture names when content capture is enabled.
FailuresException, failing phase, OpenTelemetry error status, status_code, and error.message.
xdistEach 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.

1import pytest
2from respan import task
3
4
5@task(name="calculate_checkout_total")
6def calculate_total(amount: int, tax_rate: float) -> float:
7 return round(amount * (1 + tax_rate), 2)
8
9
10@pytest.mark.parametrize(
11 ("amount", "tax_rate", "expected"),
12 [(100, 0.05, 105.0), (80, 0.10, 88.0)],
13)
14def test_checkout_total(amount, tax_rate, expected):
15 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:

1import asyncio
2
3import pytest
4
5
6@pytest.mark.asyncio
7async def test_async_lookup():
8 await asyncio.sleep(0)
9 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:

$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:

$pytest --trace-config --respan-tracing

The runnable example suite includes successful, asynchronous, skipped, expected-failure, content-control, and deterministic failure runs.