Metadata-Version: 2.4
Name: pytest-testpulse
Version: 1.0.0
Summary: Report pytest results into TestPulse via its JUnit-XML import API
Author: TestPulse
License: MIT
Project-URL: Homepage, https://github.com/Barayo/pytest-testpulse
Classifier: Framework :: Pytest
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Requires-Python: >=3.9
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: pytest>=7.0
Requires-Dist: requests>=2.28
Provides-Extra: dev
Requires-Dist: responses>=0.23; extra == "dev"
Requires-Dist: pytest-cov>=4.0; extra == "dev"
Requires-Dist: python-semantic-release>=9.0; extra == "dev"
Dynamic: license-file

# pytest-testpulse

A pytest plugin that reports test results into [TestPulse](https://github.com/Barayo/TestPulse)
via its JUnit-XML import API, matching each test to an existing case by key.

## Install

```bash
pip install pytest-testpulse
```

## Usage

Mark a test with the case key it corresponds to in TestPulse:

```python
import pytest

@pytest.mark.testpulse("LOGIN-42", platform="linux", tags=["smoke"])
def test_login_succeeds():
    ...
```

Run with `--junitxml` — the plugin reads pytest's own JUnit XML output rather
than generating it itself, so this flag is required whenever the marker (or
`--testpulse-dry-run`) is in use:

```bash
pytest --junitxml=report.xml
```

At the end of the session, the plugin submits `report.xml` to
`POST /api/v1/projects/{project}/imports` and prints a summary. A suite with
no marked tests and no `--testpulse-dry-run` needs no configuration at all —
the plugin does nothing.

## Configuration

Each setting resolves in this order: **CLI flag › environment variable ›
`pytest.ini`/`pyproject.toml` option** — the first one present wins.

| Setting | CLI flag | Env var | ini-option |
|---|---|---|---|
| API base URL | `--testpulse-url` | `TESTPULSE_URL` | `testpulse_url` |
| API token | `--testpulse-token` | `TESTPULSE_TOKEN` | `testpulse_token` |
| Project key | `--testpulse-project` | `TESTPULSE_PROJECT` | `testpulse_project` |
| Fail on unmatched | `--testpulse-fail-on-unmatched` / `--testpulse-no-fail-on-unmatched` | `TESTPULSE_FAIL_ON_UNMATCHED` | `testpulse_fail_on_unmatched` |

`TESTPULSE_URL`/`TESTPULSE_TOKEN` match the env var names TestPulse's own
docs already use for the raw `conftest.py` snippet, so migrating from that
snippet to this plugin doesn't require renaming CI secrets. Put your token in
an env var or CI secret — not in a committed ini-option.

`testpulse_fail_on_unmatched` (env var and ini-option) accepts
`1`/`true`/`yes` (case-insensitive) as true; anything else, including unset,
is false. `--testpulse-no-fail-on-unmatched` overrides an ini-option or env
var set to true, for a single local run.

## Exit-code behavior

| Import response | Effect on `pytest`'s exit code |
|---|---|
| `201` — every test matched | Untouched; a summary is printed to stderr. |
| `207` — some tests unmatched | Untouched by default (a warning is printed, naming the unmatched case keys and `--testpulse-fail-on-unmatched` as the escalation path). Set `--testpulse-fail-on-unmatched` to make this a hard failure instead. |
| Network error, auth failure, non-2xx | **Always** fails the session — this isn't suppressible, since a broken submission is a different problem than an incomplete case-annotation rollout. |

## Dry run

`--testpulse-dry-run` previews which annotated case keys would match, via a
**read-only** fetch of the project's cases — it never calls the import
endpoint, never creates a run, and never affects the exit code:

```bash
pytest --junitxml=report.xml --testpulse-dry-run
```

## Attachments

Call the `testpulse_attach` fixture from inside a marked test to attach a
screenshot or file to its case:

```python
@pytest.mark.testpulse("LOGIN-42")
def test_login_succeeds(testpulse_attach):
    ...
    testpulse_attach("failure.png")
```

Calling it on a test with no `@pytest.mark.testpulse` marker drops the
attachment with a warning — there's no case key to attach it to. If a
marked test's case comes back unmatched in a `207` response, its attachment
is named separately in the warning output, distinct from the plain
unmatched-case listing.

## License

MIT
