Local JSON report schema¶
This report helps investigate the individual pytest cases behind aggregate Jira Test Run results. It is an audit/debugging facility within the integration.
pytest --xray-case-report=cases.json writes a local JSON report without
requiring Jira markers, credentials, --jira-xray, or duplicate-ID overrides.
It has no transport. Do not submit this schema to Xray's import endpoint.
The model in case_results.py depends only on Python's standard library.
case_report.py adapts pytest reports to it and writes once on the controller
at session end. The report preserves each executed node ID as a distinct case,
pytest's effective parameter ID (callspec.id), and all observed attempts.
User-supplied IDs are retained as pytest resolves them; duplicate IDs receive
pytest's suffixes. Non-parametrized tests have parameter_id: null.
Each attempt stores its reporting worker and an ordered phase list. Each phase
has phase, normalized outcome, raw pytest_outcome, and duration in seconds.
The adapter snapshots the original outcome before rerunfailures changes its
raw report outcome to rerun. Thus a retry retains outcome: failed or error
with pytest_outcome: rerun, rather than replacing it with the eventual pass.
Parameter privacy¶
Parameter values are absent by default. --xray-case-values separately opts
into scalar values: string, boolean, integer, finite float and null. Other
values get a placeholder; objects are not repr'd and containers are not traversed.
No assertion text, traceback, capture, skip reason or xfail reason is stored.
However, pytest node IDs/parameter IDs may themselves contain values; choose
safe explicit IDs. This report is not a redaction guarantee. The older
--xray-parameter-details comment fallback is a separate opt-in that includes
scalar values; enabling a case report does not enable that fallback.
Optional redacted identities¶
--xray-case-redact-identities replaces node IDs with case-1, case-2, etc.,
and parameter IDs with corresponding opaque aliases in this offline file.
The controller assigns aliases, so retry grouping and worker ownership survive.
Aliases are local to one run, follow arrival order, and cannot be used to match
cases across runs. Original IDs are not included in this file. Combining this
mode with --xray-case-values is rejected to avoid accidentally reintroducing
values. Original identities still exist inside pytest, worker communication,
console output, other reporters and any separately enabled Xray comment output.
This option redacts only the offline case report, not the entire test process.
Two-worker and local failed-then-passed reruns exercise this mode in tests.
Aggregation rules¶
- Every received phase is appended. A setup phase starts an attempt; teardown
closes it. A phase without a current attempt starts an incomplete observation.
Grouping uses
(nodeid, worker_id)so workers cannot overwrite one another. - Attempt numbers are local to a case, in controller observation order. They are not globally stable execution IDs or cross-session retry identifiers.
- Failed call, setup/teardown error, strict XPASS, or an otherwise unclassified
rerunmakes an attempt failed. Failure takes priority over skips and passes. - Skip and XFAIL summarize as skipped; non-strict XPASS summarizes as passed.
Their exact normalized outcomes remain available in phase records. Strict
XPASS is recognized from pytest's standard
[XPASS(strict)]representation; a nonstandard failure representation remains a failure, not a pass. - A passing call requires a teardown observation for a complete passed attempt. Unknown phase outcomes or unfinished non-failing attempts are incomplete.
- Across attempts: failed > incomplete > skipped > passed. A failure followed
by a successful retry therefore remains failed in the case summary. The
separate
pytest_exit_statusrecords pytest's actual session result, which may be zero after successful reruns. This summary does not alter pytest's exit code; report-write failure does (INTERNAL_ERROR).
Cases are sorted by node ID. Attempts/phases retain arrival order. With xdist, arrival order across workers may vary. Output uses a temporary file and atomic replacement; an unwritable destination is reported and exits nonzero. Parent directories must exist. A previous complete file can remain after a write failure: callers must check exit status. Reusing a path replaces the previous session report, not appends to it; choose separate paths for separate runs.
Exercised scope and limits¶
Tested on Python 3.12, pytest 9.1.1, pytest-xdist 3.8.0 and
pytest-rerunfailures 16.7: ordinary execution, two-worker load scheduling,
--dist=each with the same node on both workers, failed-call reruns locally and
with two workers, and a setup-failure rerun locally. Tests cover phase errors,
skip/XFAIL/XPASS, duplicate parameter IDs, sensitive-value exclusion, write
errors and simultaneous legacy Xray JSON output.
This is not a claim of compatibility with every plugin/version or scheduler. Worker crashes/restarts, teardown-failure reruns, pytest-repeat, subtests, arbitrary third-party retry plugins, and hard process kills are not exercised. Only received test reports are represented: deselected/unexecuted cases, collection errors and worker crashes do not gain fabricated case results. Session exit status remains essential when interpreting partial output. The adapter does not change the inherited legacy Xray publisher's rerun or status semantics. No native Xray reporting is implied by these offline tests.