Metadata-Version: 2.4
Name: m00nreport-pytest
Version: 1.1.0
Summary: Official pytest plugin for M00N Report, an AI-native test management platform - streams live results, steps, retries and attachments while the suite runs, and links each test to the case it covers.
Author: M00N Report
License: MIT
Project-URL: Homepage, https://m00nreport.com
Project-URL: Documentation, https://m00nreport.com/documentation/reporters/pytest
Project-URL: Repository, https://github.com/m00nreport/pytest-reporter
Project-URL: Issues, https://github.com/m00nreport/pytest-reporter/issues
Keywords: pytest,pytest-plugin,reporter,test-reporting,testing,test-automation,test-management,test-case-management,TMS,qa,m00nreport,playwright,pytest-xdist,ci,test-results
Classifier: Framework :: Pytest
Classifier: Programming Language :: Python :: 3
Classifier: Topic :: Software Development :: Testing
Requires-Python: >=3.9
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: pytest>=7.0
Requires-Dist: pluggy>=1.2
Provides-Extra: playwright
Requires-Dist: pytest-playwright>=0.5; extra == "playwright"
Provides-Extra: dev
Requires-Dist: pytest-rerunfailures>=12.0; extra == "dev"
Requires-Dist: pytest-playwright>=0.5; extra == "dev"
Dynamic: license-file

# m00nreport-pytest

[![PyPI](https://img.shields.io/pypi/v/m00nreport-pytest.svg)](https://pypi.org/project/m00nreport-pytest/)
[![CI](https://github.com/m00nreport/pytest-reporter/actions/workflows/ci.yml/badge.svg)](https://github.com/m00nreport/pytest-reporter/actions/workflows/ci.yml)
[![python](https://img.shields.io/pypi/pyversions/m00nreport-pytest.svg)](https://pypi.org/project/m00nreport-pytest/)
[![license](https://img.shields.io/pypi/l/m00nreport-pytest.svg)](https://github.com/m00nreport/pytest-reporter/blob/main/LICENSE)

Official pytest plugin for [M00N Report](https://m00nreport.com), an AI-native test management platform. Results, steps, attachments and retries land in M00N Report while the suite is still running, and each test can carry the manual test case it covers. A reporting problem is never a test failure: every hook is guarded, and the worst case is a single `[m00nreport]` warning on stderr.

## Requirements

Python 3.9 or newer, pytest 7.0 or newer, and pluggy 1.2 or newer.

pip installs pluggy for you, so the floor is stated only because it bites when a lockfile gets it wrong: pytest 7 alone permits pluggy 0.12, and this plugin uses `@pytest.hookimpl(wrapper=True)`, which is pluggy 1.2 API and raises at import below it. A `pytest11` plugin that raises at import takes down every pytest invocation in that environment, including suites that have nothing to do with reporting.

The Playwright layer additionally needs `pytest-playwright`, and retries need `pytest-rerunfailures`. Both are optional.

## Installation

```bash
pip install m00nreport-pytest

# with the Playwright layer
pip install "m00nreport-pytest[playwright]"
```

The package registers itself through the standard `pytest11` entry point, so pytest loads it on every run once it is installed. There is no `conftest.py` wiring, no `-p` flag, no config block.

## Quick Start

### 1. Get your API key

In M00N Report, open **Project Settings -> API Keys** and copy the key. It starts with `m00n_` followed by 48 hex characters, and it identifies both the organization and the project.

Keep it out of the repository. In CI it belongs in a secret; locally, in an environment variable.

### 2. Point the plugin at your project

```bash
export M00N_SERVER_URL=https://m00nreport.com   # or your own instance, root origin, no /api, no trailing slash
export M00N_API_KEY=m00n_...
```

PowerShell:

```powershell
$env:M00N_SERVER_URL = "https://m00nreport.com"
$env:M00N_API_KEY = "m00n_..."
```

Everything except the key can live in the repository instead:

```ini
# pytest.ini
[pytest]
m00n_launch = Nightly regression
m00n_tags = nightly,regression
```

The same keys work in `setup.cfg` under `[tool:pytest]` and in `pyproject.toml` under `[tool.pytest.ini_options]`.

### 3. Run your tests

```bash
pytest
```

```
[m00nreport] Run started: 9f3c1a7e-4d21-4b8a-9e77-2c5b1d0a6f84

--------------------------------- M00N Report ---------------------------------
  Run ID:    9f3c1a7e-4d21-4b8a-9e77-2c5b1d0a6f84
  Tests:     3/3 reported
  Failed:    1
```

The run id is printed the moment the run starts, and again in the summary at the end. Both lines carry the same id, which is the run in the project's **Launches** list, filling in as tests finish. No `[m00nreport]` line at all means the plugin never activated - see [Troubleshooting](#troubleshooting).

## Configuration

Every option can be set three ways. Precedence, highest wins: **CLI flag > `pytest.ini` key > environment variable > default.**

| CLI flag | ini key | Env var | Default | Description |
|---|---|---|---|---|
| `--m00n-server-url` | `m00n_server_url` | `M00N_SERVER_URL` | required | Your M00N Report URL |
| `--m00n-api-key` | `m00n_api_key` | `M00N_API_KEY` | required | Project API key (`m00n_...`) |
| `--m00n-launch` | `m00n_launch` | `M00N_LAUNCH` | `Run <date>` | Title for this run |
| `--m00n-tags` | `m00n_tags` | `M00N_TAGS` | `[]` | Comma-separated tags |
| `--m00n-attributes` | `m00n_attributes` | `M00N_ATTRIBUTES` | `{}` | JSON object of custom run attributes. Malformed JSON is ignored rather than crashing the run. |
| | | `M00N_ATTRIBUTE_<NAME>` | | One attribute per variable, which is easier to set in a CI job than a JSON object quoted inside a YAML value. `M00N_ATTRIBUTE_BRANCH=main` becomes the attribute `branch`, and wins over the same key in `M00N_ATTRIBUTES`. |
| `--m00n-debug` | `m00n_debug` | `M00N_DEBUG` | `false` | Print a `[m00nreport]` line on stdout for every HTTP retry and any final exhaustion. Silent when off. |
| `--m00n-disable` | | | `false` | Turn the plugin off for this invocation even when a URL and key are set. CLI only, by design. |

The plugin activates only once both the URL and the key resolve to a value.

**A discovery pass creates no launch.** `--collect-only`, `--setup-only` and `--setup-plan` run no tests, so the plugin stays out of the way entirely. Without that rule an IDE refreshing its test tree on every save fills the Launches list with empty runs.

## Steps

```python
from m00n_reporter import step

def test_checkout():
    with step("Add item to cart"):
        cart.add(sku="A-1")

    with step("Pay"):
        with step("Enter card"):
            checkout.enter_card(EXPIRED_CARD)
        checkout.submit()
```

Steps nest, and their timings are recorded. A step still open when the test fails is reported as skipped, so the step list shows how far the test got.

## Attachments

```python
from m00n_reporter import attach

def test_api_contract():
    response = client.get("/api/cart")

    attach(response.content, name="cart.json", content_type="application/json")
    attach("artifacts/screenshot.png")           # a path works too
```

Bytes need a `name`; for a path it defaults to the basename. Content type is guessed from the name when you do not pass one.

A file over 200MB is skipped with one warning (recount: `grep -n 'MAX_ATTACHMENT_SIZE =' src/m00n_reporter/attachments.py`). When a run reaches your plan's attachment quota the plugin warns `run attachment limit reached (N/M MB used)` and drops the rest of that run's uploads, including ones already queued. Neither ever fails the test.

## Linking Tests to Cases

A `m00n` marker attaches the result to a manual test case, which is what turns a run into coverage rather than a list of green ticks.

```python
import pytest

@pytest.mark.m00n(case_id=42, tags=["payments", "smoke"])
def test_checkout_rejects_expired_card():
    ...
```

`case_id` is the case's internal id, not the `TC-42` number shown in the UI. Open the case in M00N Report and take the id from the address bar. Tags here are per test, on top of the run-level `--m00n-tags`.

## Playwright Integration

With `pytest-playwright` installed, the plugin instruments the `page` fixture on its own. There is nothing to enable and no base class to inherit.

```python
def test_login(page):
    page.goto("https://example.com/login")   # each of these becomes a step
    page.fill("#email", "user@example.com")
    page.click("button[type=submit]")
```

Direct `page` calls - `goto`, `click`, `fill`, `press`, `wait_for_selector` and the rest of the Page API - become steps carrying their selector and timing, and a screenshot is captured and attached when a test fails.

Locator calls do not become steps. `page.get_by_role("button").click()` goes through a `Locator`, which the plugin does not wrap; put those inside `with step(...)` if you want them in the report.

Video and trace files are uploaded too, on failing tests only, and only if you asked pytest-playwright to record them - both are `off` by default:

```bash
pytest --video=retain-on-failure --tracing=retain-on-failure
```

## Retries

With `pytest-rerunfailures`, every attempt is reported, not only the final verdict, so a flaky test is visible as a flaky test rather than as a pass.

```bash
pytest --reruns 2
```

The summary counts attempts, so one test that passed on its third try reads as `Tests: 3/3 reported`.

## Parallel Runs

Under `pytest-xdist` the whole run is a single launch, with no configuration: the controller starts it, every worker reports its own slice into it, and the controller closes it. Only the controller prints the run id and the summary, so `-n 8` does not print eight of them.

```bash
pytest -n auto
```

## CI Auto-Detection

Running in GitHub Actions, GitLab CI, Jenkins, Bitbucket Pipelines, Azure DevOps, CircleCI or Travis CI needs no configuration: branch, commit, pipeline, build number, build URL and who triggered it are detected and attached to the run. Anything you set through `--m00n-attributes` or `M00N_ATTRIBUTE_<NAME>` overrides a detected value.

```yaml
# .github/workflows/tests.yml
name: Tests
on: push

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: '3.12'
      - run: pip install -r requirements.txt
      - run: pytest
        env:
          M00N_SERVER_URL: https://m00nreport.com
          M00N_API_KEY: ${{ secrets.M00N_API_KEY }}
```

## What Gets Sent

To your server URL over HTTPS: test node ids and file paths, step names and timings, outcomes, error messages and stack traces, the CI attributes above, and whatever you pass to `attach()`.

A stack trace is pytest's own long representation of the failure - the same text pytest prints in its FAILURES section - so it carries the source lines around the failing assertion.

Not sent: environment variables beyond the CI attributes above. The API key is never printed, including under `--m00n-debug`.

## Troubleshooting

Warnings go to stderr, prefixed `[m00nreport]`. The run id line, the summary and every `--m00n-debug` line go to stdout, so redirecting only stderr to a file captures the warnings and loses the run id. The suite stays green in every case below.

| Message or symptom | Cause |
|---|---|
| `serverUrl and apiKey are required. Reporter disabled.` | Exactly one of the two resolved, which usually means a typo in an environment variable name. |
| `Server unavailable (<url>). Reporter disabled - tests will continue without reporting.` | Nothing answered at that URL. Check for a typo, an `/api` suffix, or a host reachable only over VPN. |
| `Unauthorized. Reporter disabled.` | The key was rejected: unknown, revoked, or belonging to an inactive organization. The wording is the server's own, so another rejection reads differently, but `Reporter disabled.` always means a permanent error - attempted once, never retried. |
| `HTTP 503. Reporter disabled for this run.` | The server answered its health check but could not start the run, after three attempts. Most often a deploy in progress. |
| `Multiple service failures detected. Disabling reporter for remaining tests.` | Five consecutive service failures tripped the breaker. The run is reported up to that point and no further. |
| No `[m00nreport]` line and no run in the app | Neither the URL nor the key resolved, so the plugin stayed silently inactive. This is the expected state on a developer machine with nothing configured. In CI it is most often a forked pull request: GitHub does not expose secrets to them, so `secrets.M00N_API_KEY` is an empty string. |
| A run appears, but with no steps from Playwright | `pytest-playwright` is not installed, so the layer stayed off. Install the `[playwright]` extra. |
| Retries appear as a single result | `pytest-rerunfailures` is not installed, so pytest never reran the test. |

`--m00n-debug` prints every HTTP retry and any final exhaustion, which distinguishes "never tried" from "tried and could not reach the server".

## Known Limitations

- **Teardown-phase failures are not reported.** If the test body passes but a fixture teardown raises afterwards, the test keeps the status it already reported at call time.
- **Runs are not linked to releases.** The plugin reports a launch; attaching launches to a release is done in the app or over [MCP](https://m00nreport.com/documentation/mcp/quick-start).

## Support

- [Package on PyPI](https://pypi.org/project/m00nreport-pytest/)
- [Documentation](https://m00nreport.com/documentation/reporters/pytest)
- [Report Issues](https://github.com/m00nreport/pytest-reporter/issues)
- [Community Discord](https://discord.gg/hzZvyVWS3Q)

## License

MIT License. See [LICENSE](https://github.com/m00nreport/pytest-reporter/blob/main/LICENSE).
